-
- Language: C++
- IDE: Visual Studio
- Yolov4 / Darknet / OpenCV / YoloLabel
- Developer: 김주하
-
- Class Num: 5
- Class Name: Person, Helmet, Vest, Boots, Glove
- Batch / Subdivision: 64 / 32
- Max_Batches / Step: 15000 / 12000, 13500
- Filters: 30
- Width & Height: 320
- Number of dataset: About 1000 pictures
-
데이터 학습 및 추론
- YoloLabel로 라벨링된 데이터를 다크넷으로 학습시킴
- 예측 완료된 객체 정보를 person 안의 안전 장비를 분류하는 함수로 보냄
-
안전 장비 분류
- 내부 안전 장비에 따라서 색상을 분류함
- 분류 결과를 저장하여 촬영 함수에서 사용할 수 있도록 함
-
화면 출력
- 분류된 결과에 따라 바운딩 박스가 그려진 화면을 출력함
-
여러 함수들에서 사용해야 하는 것들과 포함해야 되는 것들을 외부에 선언해 줌
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <opencv2/opencv.hpp> #include <ctime> using namespace std; using namespace cv; using namespace cv::dnn; const float CONFIDENCE_THRESHOLD = 0.3; const float NMS_THRESHOLD = 0.5; const int NUM_CLASSES = 5; const Scalar colors[] = { {0, 0, 255},{0, 94, 255}, {166,97,243},{22, 219, 29}};//바운딩 박스를 그릴 빨강, 주황, 핑크, 초록 색상 Mat frame, blob; vector<Mat> detections; bool wear; void trainandpredict(); void findClass(); void print(); void cheese();
-
카메라 입력을 받아오는 것과 블롭화, 딜레이 기능을 제외한 기능을 함수로 선언해 줌
int main() { VideoCapture cap(0); if (!cap.isOpened()) { cerr << "Camera open failed!" << endl; return -1; } TickMeter tm; while (true) { static double ms = 0; static bool first = false; tm.start(); cap >> frame; if(frame.empty()) break; blobFromImage(frame, blob, 1 / 255.f, Size(320, 320), Scalar(), true, false, CV_32F); trainandpredict();//학습 및 추론 findClass();//객체 검출 & 색상 분류 print();//출력 if(!wear) { if(!first) { cheese(); ms = 0; first = true; } else { if (ms > 60) { cheese(); ms = 0; } } }//사진이 찍히면 타이머 0으로 초기화 tm.stop(); ms += tm.getTimeSec();//사진이 찍힌 후 시간 계산 tm.reset(); if (waitKey(1) == 27) break; } return 0; }
-
앞서 학습된 데이터로 추론하고 TickMeter를 이용하여 추론 시간을 측정한 후 출력함
void trainandpredict() { TickMeter tm; auto net = readNetFromDarknet("yolov4-safetyequipment.cfg", "yolov4-safetyequipment_final.weights"); net.setPreferableBackend(DNN_BACKEND_OPENCV); net.setPreferableTarget(DNN_TARGET_CPU); auto output_names = net.getUnconnectedOutLayersNames(); tm.start(); net.setInput(blob); tm.stop(); double ms = tm.getTimeMilli(); cout << "연산 속도: " << ms << endl; net.forward(detections, output_names); }
-
예측된 객체의 정보(바운딩 박스의 중심축, 크기, 클래스 신뢰도)를 vector에 저장함
-
Person 클래스가 검출되면 클래스의 바운딩 박스 안에 있는 안전 장비를 파악하여 Person 바운딩 박스의 색을 변경함
void findClass() { vector<int> indices[NUM_CLASSES]; vector<Rect> boxes[NUM_CLASSES]; vector<float> scores[NUM_CLASSES]; for (auto& output : detections) { const auto num_boxes = output.rows; for (int i = 0; i < num_boxes; i++) { auto x = output.at<float>(i, 0) * frame.cols; auto y = output.at<float>(i, 1) * frame.rows; auto width = output.at<float>(i, 2) * frame.cols; auto height = output.at<float>(i, 3) * frame.rows; Rect rect(x - width / 2, y - height / 2, width, height); for (int c = 0; c < NUM_CLASSES; c++) { auto confidence = *output.ptr<float>(i, 5 + c); if (confidence >= CONFIDENCE_THRESHOLD) { boxes[c].push_back(rect); scores[c].push_back(confidence); } } } } vector<string> class_names = { "person","helmet", "vest", "boots", "glove" }; for (int c = 0; c < NUM_CLASSES; c++) NMSBoxes(boxes[c], scores[c], 0.0, NMS_THRESHOLD, indices[c]); wear = 1; for (int c = 0; c < NUM_CLASSES; c++) { for (int i = 0; i < indices[c].size(); ++i) { auto idx = indices[c][i]; const auto& rect = boxes[c][idx]; if (c == 0) { int n = 0;//기본 빨강색 bool equipment[4] = { 0,0,0,0 }; bool important = 0; for (int d = 1; d < NUM_CLASSES; d++) { for (int j = 0; j < indices[d].size(); ++j) { auto idx_equip = indices[d][j]; const auto& rect_equip = boxes[d][idx_equip]; int x = rect_equip.x + (rect_equip.width / 2); int y = rect_equip.y + (rect_equip.height / 2); if (x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height) equipment[d - 1] = true;//각각의 장비가 있으면 true로 표시 } } if (equipment[0] && equipment[1]) {//안전모, 안전조끼 둘 다 있으면 important[1] = true; important = true; } else wear = 0; if (important)//안전모와 조끼가 있을 때 { if (equipment[2] && equipment[3]) n = 3;//장갑, 장화 둘 다 있으면 초록 else if (equipment[2] || equipment[3]) n = 2;//장갑, 장화 둘 중 하나만 있으면 핑크 else n = 1;//장갑, 장화 둘 다 없으면 주황 }//안전모와 조끼 중 하나라도 없으면 빨강 rectangle(frame, Point(rect.x, rect.y), Point(rect.x + rect.width, rect.y + rect.height), colors[n], 2); string label_str = class_names[c] + ": " + format("%.02lf", scores[c][idx]); int baseline; auto label_bg_sz = getTextSize(label_str, FONT_HERSHEY_PLAIN, 1, 2, &baseline); rectangle(frame, Point(rect.x, rect.y - label_bg_sz.height - baseline), Point(rect.x + label_bg_sz.width, rect.y), colors[n], FILLED); putText(frame, label_str, Point(rect.x, rect.y - baseline), FONT_HERSHEY_PLAIN, 1, Scalar(0, 0, 0)); } } } }
-
time_h 객체를 선언한 후 출력되는 화면에 현재 시간을 함께
void print()//화면 출력 함수 { time_t timer; struct tm* t; timer = time(NULL); // 1970년 1월 1일 0시 0분 0초부터 시작하여 현재까지의 초 t = localtime(&timer); string cur_time = to_string(t->tm_year + 1900) + "." + to_string(t->tm_mon + 1) + "." + to_string(t->tm_mday) + " " + to_string(t->tm_hour) + ":" + to_string(t->tm_min) + ":" + to_string(t->tm_sec); int baseline; auto time_sz = getTextSize(cur_time, FONT_HERSHEY_PLAIN, 1, 1, &baseline); rectangle(frame, Point(0, 0), Point(0 + time_sz.width, time_sz.height + 5), Scalar(140,140,140), FILLED); putText(frame, cur_time, Point(0, time_sz.height +2), FONT_HERSHEY_PLAIN, 1, Scalar(255, 255, 255)); imshow("output", frame); }
-
현재 출력된 화면을 폴더에 저장함
void cheese() { static int cnt = 0; vector<int> params; params.push_back(IMWRITE_JPEG_QUALITY); params.push_back(95); imwrite(to_string(cnt++)+".JPG", frame, params); }
- 전체 구현 영상
- 객체 검출
사람, 안전모, 안전조끼, 안전화, 장갑을 분류함
일부 장비를 검출하지 못하는 경우가 있음
- 장비 분류
사람 클래스 안에 안전 장비의 중심 좌표를 파악함
넷 다 있으면 초록, 안전모, 조끼가 없으면 빨강
둘 다 있는 경우에 안전화, 장갑 둘 다 없으면 주황, 둘 중 하나만 없으면 핑크
- 촬영
빨간색 바운딩박스가 존재하면 첫 번째는 바로 화면을 폴더에 저장함
촬영 이후 60초를 측정해 1분이 지났을 때 다시 저장되게 딜레이 설정
-
학습 데이터 부족 문제
darknet 학습시에 한 클래스당 수천 개의 데이터를 넣어야 학습이 되는데 수백 개만 넣음
초반 person 데이터 없이 진행했을 때 사람 머리를 안전모로 인식 -> person 데이터 추가 -
안전화 정의 안 됨
공사장이나 특정 기관에서 안전화를 특정짓지 않음(운동화, 부츠, 워커 등 다양)
장화, 워커 등 발목 위로 올라가는 신발 기준으로 데이터 학습 -
안 썼을 때도 인식
안전모를 들고 있을 때도 person 클래스 안에 있기 때문에 인식됨
no helmet, helmet 등으로 학습시켜야 했음 -
멀면 인식 안 됨
데이터 부족으로 근거리에 있는 사진 위주로 학습시킴
가까울 때는 인식이 되지만 거리가 멀어지면 인식이 잘 안