-
Day11교육/서울 ICT 이노베이션 고오급 시각과정 2021. 6. 30. 13:59728x90
void qrcode_detector() { Mat src = imread("images/qrcode.jpg"); Mat gray; cvtColor(src, gray, COLOR_BGR2GRAY); Mat bin; //이미지 이진화 threshold(gray, bin, 128, 255, THRESH_BINARY); //Canny(gray, bin, 50, 200); 혹시나 너무 어둡거나 그늘진데서 썼을때 캐니를 쓰면 됨 imshow("bin", bin); waitKey(0); //vector<vector<Point>> : 리스트의 리스트의 포인터 //하나의 외곽선 : 포인터들의 집합->리스트의 포인터 //외곽선은 여러개 존재->리스트의 리스트의 포인터 vector<vector<Point>> contours; vector<Vec4i> hierarchy; //칸투어들의 구조화된 정보 //외곽선 검출 //외곽선은 닫힌 구조로 외곽선 안에 외곽선 안에 외곽선,,,->트리 구조로 나타낼 수 있음 : hierarchy //트리구조할지말지 : RETR_TREE //CHAIN_APPROX_NONE : 외곽선을 추출할 때 최적화할수있는 알고리즘 findContours(bin, contours, hierarchy, RETR_TREE, CHAIN_APPORX_NONE, Point(0, 0)); //모든 칸투어 그리기 Mat dst = Mat::zeros(gray.size(), CV_8UC3); //칸투어를 그릴 빈 캔버스 for (int i = 0; i < contours.size(); i++) { //칸투어를 단순화시키는 함수 vector<Point>app; //arcLength():외곽선길이출력 //외곽선의 2%를 thresold로 준다->외곽선의 점을 찍는 걸 50개 밑으로 //2~50개 안으로 int thres = max((int)(arcLength(contours[i], true) * 0.02), 2); approxPolyDP(contours[i], app, thres, true); //Ramer-Douglass-Peucker알고리즘 contours[i] = app; Scalar c(rand() % 160, rand90 % 160, rand() % 160); //랜덤한 rgb값(그릴 칸투어의 색깔) drawContours(dst, contours, i, c, 2); //i번째 칸투어를 찾아서 그려줌 } imshow("dst", dst); waitKey(0); //사각형만 그리기 for (int i = 0; i < contours.size(); i++) { auto& con = contours[i]; if (con.size() == 4) { drawContours(dst, contours, i, Scalar(255, 255, 255), 2); } } imshow("dst", dst); waitKey(0); //부모와 자신, 자녀 모두 사각형인 도형만 그리기 for (int i = 0; i < contours.size(); i++) { //auto&==vector<Point>& auto& con = contours[i]; Vec4i& hie = hierarchy[i]; int a = hie[3]; //부모가 없거나 자식이 없으면 continue if (hie[3] == -1 || hie[2] == -1) continue; auto& parent = contours[hie[3]]; auto& child = contours[hie[2]]; //qr코드 사각형의 비율내면 if (parent.size() == 4 && child.size() == 4 && con.size() == 4) { /*double parentLen = arcLength(parent, true); double conLen = arcLength(con, true); double childLne = arcLength(child, true); printf("%lf %lf \n", parentLen/conLen, conLen/childLen);*/ drawContours(dst, contours, i, Scalar(0, 255, 255), 2); } } }
findContours() RANSAC알고리즘
그림에서 outlier라고 표시된 부분과 같은 이상치를 제외하고 inlier만 이용해서 간격을 도출해내고 싶을 때 사용하는 알고리즘 중 하나
랜덤으로 두 점을 골라서 모델을 만들고 inlier의 점의 개수를 뽑고 점들의 개수가 가장 많을 때가 최적의 해
MeanShift?
Meanshift알고리즘
1. 랜덤으로 시작점을 정함
2. 시작점을 기준으로 주위에 일정 threshold범위내에서 점의 중심을 찾음
3. 새로 찾은 점의 중심을 기준으로 다시 2를 반복
4. 점의 중심이 더 이상 움직이지 않을 때까지 실행
왼쪽 그림에서 보면 1->2->3->4
meanshift의 한계 : 주변에 inlier가 없으면 중심에 가기 전에 끝날 수 있음
->meanshift를 여러번 하자! = RANSAC
MeanShift는 끝까지 가지만 RANSAC은 MeanShift를 1번(또는 2번)정도만
객체검출
1. 템플릿 매칭
템플릿 : 찾고자 하는 대상이 되는 작은 크기의 영상
템플릿 매칭 : 작은 크기의 템플릿 영상을 입력 영상 전체 영역에 대해 이동하면서 가장 비슷한 위치를 수치적으로 찾아내는 방식 ex)sliding window(detection하는 가장 기초적인 방법, 분류기를 이용해서 검출기를 만들 수 있음)
element wise연산을 사용해서 검출함 ex) (∑(source-target)^2)/전체 크기 -> 픽셀마다 같은지 다른지 체크해서 원본과 샘플 영상이 같은지 확인
원본영상이 회전하거나 크기가 다르거나 밝기나 색상이 다르거나 등 조금만 바뀌면 취약함 걍 다 취약 실무에선 잘 안씀
그래서 영상을 이진화해서 템플릿 매칭함
//이진화 - 밝기와 상관없이 템플릿 매칭을 하기 위해 adaptiveThreshold(gray, bin, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, 7, 10); //계산 편하게 하기 위해 밝은건 1 어두운건 0으로, 위에선 밝은거 255해놨는데 이러면 너무 큰값으로 됨 adaptiveThreshold(gray, bin, 1, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, 7, 10);
이진화해서 만든 원본영상 템플릿 TemplateMatchModes 열거형 상수
- TM_SQDIFF : 제곱차 매칭 방법≒MSE
배경이랑 찾으려는 템플릿이랑 전부 매칭, 일반적으로 이걸 많이 사용 - TM_CCORR : 상관관계 매칭 방법
배경은 제외하고 템플릿에만 초점을 맞춰서 매칭, 템플릿만 매칭되면 된다할 때 사용
matchTemplate(bin, temp, res, TM_SQDIFF); //임계값을 주어 원본영상에서 매칭된 부분확인 threshold(res, found, 150, 255, THRESH_BINARY);
TM_SQDIFF를 사용해서 템플릿 매칭을 했기 때문에
매칭률이 높은 곳일수록 어둡게,
매칭률이 낮은 곳일수록 밝게 나옴
왼쪽의 이미지는 임계값(150)을 설정하여
임계값이하인 부분만 뽑아서 본 이미지
검출된 부분이 점 하나로 나온 게 아니라
여러 개의 점들로 이루어진 것을 알 수 있는데
슬라이딩 윈도우를 하면서 템플릿을 옮겨가며 검출하는데
윈도우내에 템플릿이 있으면 다 검출해서 여러개가 찍히는 것
이 중에서 가장 적합한 것 하나만 찾아야하는데 이 기법을 NMS(Non-Maximum Suppression)이라고 함
vector<Point> non_maximum(Mat src) { vector<Point> result; //최종적으로 가져와야할 point int width = src.cols; int height = src.rows; int ws = src.step1(); float* dat = (float*)src.data; vector<Vec3f> points; //threshold이상인 point들을 모음 for (int y = 0; y < height; y++) { float* row = dat + y * ws; for (int x = 0; x < width; x++) { if (row[x] < 150) { points.push_back(Vec3f(row[x], x, y)); //매칭강도(0에 가까울수록 매칭O, 클수록 매칭X), x좌표, y좌표 } } } //매칭강도순으로 정렬 std::sort(points.begin(), points.end(), _sort_by_value); //points에 있는 모든 점들에 대해 for (const Vec3f& v : points) { bool found = false; //다른 모든 점들이랑 비교 for (const Point& p : result) { //l1 norm, 점과 점이 겹치는지? if (abs(v[1] - p.x) + abs(v[2] - p.y) < 20) { found = true; break; } } //겹치면 continue if (found) { continue; } //겹치지 않으면 result에 저장 result.push_back(Point(v[1], v[2])); } return result; //그렇게 검출된 매칭된 사각형의 왼쪽 위 점들 }
vector<Point> points = non_maximum(res); Mat dst = img.clone(); for (const Point& p : points) { rectangle(dst, p, p + Point(temp.cols, temp.rows), Scalar(0, 255, 0), 2); } imshow("dst", dst); waitKey(0);
- TM_SQDIFF : 제곱차 매칭 방법≒MSE