테스트장소: 카페

 

1.두 이미지간 ORB 매칭하면서 나오는 출력 영상에서 두 이미지가 동시에 나오기에, 주요한 실시간 이미지만을 ROI로 축출함.

 

 2. 해당 ROI 실시간 이미지에서 Contour함수와 approxPolyDP함수 이용해 4개의 꼭지점 좌표 얻음. 

(왜 매칭 된 좌표들을 이용하지 않았나? 앞서 두 이미지가 하나의 이미지로 동시에 나와서 사이즈크기가 컸다. 이에 매칭 좌표들 또한 +Offset이 되었다. 그렇기에 ROI 실시간 이미지에서 위 두 함수를 이용하는게 더 간단하게 구할 수 있다 생각했다)

 

3. 해당 4개의 꼭지점 인덱스가 실시간 영상 객체(핸드폰)의 기울기 부호에 따라 바뀌어지는 걸 swap 함수 통해 통일시킴.

 

4.서울 관광 영상을 실시간 영상에서 4개의 꼭짓점 에 넣어주기 위해 투시변환(getPerspectiveTransform, warpPerspective), 마스킹(bit_wise 함수)사용해 영상합성.

 

 

이슈: 실행 시 로딩시간이 좀 길다. 사용하는 cpu는 amd며 그래픽카드는 nvdia가 아니다.

ocl::setUseOpenCL(false); 또한 입력함에도 이미지를 불러오는 로딩시간이 길었다.

해당 문제는 동영상을 불러오는 로딩시간이 길다. 아무래도 동영상 용량이 커서 로딩이 길게 걸리는 듯하다.

 

 

코드가 길어진 원인은 이미지 매칭 시 하나의 window창에 2개의 이미지가 있어서 우측의 이미지를 처리할 때 어떻게 해야 단순하게 처리할지 아이디어가 생각나지 않았다. 이에 우회해서 해결을 하여 코드가 길어지게 됐다.

 

 

#include <iostream>
#include "opencv2/opencv.hpp"
#include "opencv2/core/ocl.hpp"
#include <algorithm>
/*
sequence :
homography\ 계산해 실시간 캠과 

*/

using namespace std;
using namespace cv;

bool find_homography(const Mat& REF_IMG, const Mat& CAM_ORI, Mat& src);
const Mat get_ROI(const Mat& src);
const vector<vector<Point>> getContours(const Mat& src, const Mat& dst);
vector<Point> getVertex(vector<vector<Point>> contours_points, const Mat& src);
void mergeAr(vector<Point>Vertex, VideoCapture& Video, const Mat& CAM_ORI);
int main(void)
{
	ocl::setUseOpenCL(false);
	Mat REF_IMG = imread("korea.jpg", IMREAD_GRAYSCALE); // 야경 이미지.

	if (REF_IMG.empty()) {
		cerr << "Image load failed!" << endl;
		return -1;
	}

	VideoCapture CAM(0);

	if (!CAM.isOpened()) {
		cerr << "Camera open failed!" << endl;
		return -1;
	}

	VideoCapture Video("korea.mp4");
	if (!Video.isOpened()) {
		cerr << "Video open failed!" << endl;
		return -1;
	}

	// TODO:
	while (1) {
		Mat CAM_ORI; // 실시간 영상
		CAM >> CAM_ORI;
		//REAL_FRAME과 src 사진 매칭.
		if (CAM_ORI.empty() || REF_IMG.empty()) {
			if (REF_IMG.empty()) {
				cerr << "Image can't loaded";
				return -1;
			}
			cerr << "Frame can't loaded";
			return -1;
		}
		Mat MATCHING_IMG;
		if (find_homography(REF_IMG, CAM_ORI, MATCHING_IMG) == true) {//실시간영상과 야경 이미지 매칭
			Mat ROI_IMG = get_ROI(MATCHING_IMG); // 매칭이미지가 실시간영상과 야경이미지가 함께있음에따라 
												//ROI로 실시간 영상 부분만 축출
			vector<vector<Point>> contours_points = getContours(ROI_IMG, CAM_ORI); // ROI이미지에서 contour 추출
			vector<Point>Vertex = getVertex(contours_points, CAM_ORI); // contour 좌표들을 input으로 하여 approxpoly함수를 사용, 이에 따라 꼭짓점 구함.
			//실시간 캠과 서울 영상 합성하기
			mergeAr(Vertex, Video, CAM_ORI);
		}
		int key = cv::waitKey(1);
		if (key == 27 || key == ' ') return 0; 
		imshow("Result", CAM_ORI);

	}
}
bool find_homography(const Mat & REF_IMG, const Mat & CAM_ORI, Mat & src) {
	Mat CAM_GRAY;
	cvtColor(CAM_ORI, CAM_GRAY, COLOR_BGR2GRAY);

	TickMeter tm;
	tm.start();
	Ptr<ORB> detector = ORB::create();
	vector<KeyPoint> keypoints1, keypoints2;
	Mat desc1, desc2;
	detector->detectAndCompute(REF_IMG, Mat(), keypoints1, desc1);
	detector->detectAndCompute(CAM_GRAY, Mat(), keypoints2, desc2); // 특징자, 기술자 계산
	Ptr<DescriptorMatcher> matcher = BFMatcher::create(NORM_HAMMING); // ORB 사용 시 NORM_HAMMING 인자를 줌.

	// 상위 80개 선택해서 좋은 매칭 선택
	vector<DMatch> matches;
	matcher->match(desc1, desc2, matches);

	std::sort(matches.begin(), matches.end());
	vector<DMatch> good_matches(matches.begin(), matches.begin() + 80);

	// 호모그래피 계산 코드
	//start
	vector<Point2f> pts1, pts2;
	for (size_t i = 0; i < good_matches.size(); i++) {  //good_matches.size() 80개로 가정하면. 80개를 pts1, pts2에 축적해야함.
		pts1.push_back(keypoints1[good_matches[i].queryIdx].pt); // queryIdx: 1번 영상 특징점 번호  
		pts2.push_back(keypoints2[good_matches[i].trainIdx].pt); // 2번점들 전체 특징점 정보가 keypoints2에 들어갔으며, 잘 된 매칭의 trainIdx 좌표에 point갖고와 저장.
	}

	Mat inliners;
	Mat H = findHomography(pts1, pts2, RANSAC, 3.0, inliners);  // end
	//pts1의 80개, pts2의 80개 이용해 이 둘에서 outlier 있다고 가정하여 RANSAC 기법사용해 좋은 매칭결과 반환

	int inlier_cnt = countNonZero(inliners);
	if (inlier_cnt <= 30) return false;  // Inlier 갯수 30개 미만은 검출이 안된 걸로 판단. flase 예외처리

	tm.stop();

	//cout << "time: " << tm.getTimeMilli() << endl;

	drawMatches(REF_IMG, keypoints1, CAM_GRAY, keypoints2, good_matches, src,
		Scalar(0, 0, 255), Scalar(0, 0, 255), vector<char>(),
		DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);

	//Homography 결과를 display
	vector<Point2f> corners1, corners2;
	corners1.push_back(Point2f(0, 0)); // 0 x 0
	corners1.push_back(Point2f(REF_IMG.cols - 1.f, 0)); // 가로 x 0
	corners1.push_back(Point2f(REF_IMG.cols - 1.f, REF_IMG.rows - 1.f)); // 가로 x 세로
	corners1.push_back(Point2f(0, REF_IMG.rows - 1.f)); // 0 x 세로크기
	perspectiveTransform(corners1, corners2, H);
	cout << "perspective" << endl;

	vector<Point> corners_dst;
	int x = 0;
	for (Point2f pt : corners2) {

		corners_dst.push_back(Point(cvRound(pt.x + REF_IMG.cols), cvRound(pt.y))); //실수형 -> 정수형 변환. 
	}

	polylines(src, corners_dst, true, Scalar(0, 255, 0), 2, LINE_AA); //corners2 를 display
	return true;
}
const Mat get_ROI(const Mat & src) {
	//ROI
	Mat ROI_dst = src(Rect(1279, 0, src.cols - 1279, src.rows)); // 매칭 시 하나의 화면에 이미지 2개가 있기에 실시간 캠 영역부분을 roi 로 축출했습니다.
	cout << ROI_dst.rows << "  " << ROI_dst.cols << endl;
	Mat ROI_dst2 = ROI_dst(Rect(0, 0, 640, 480));
	Mat Masking;
	ROI_dst.copyTo(Masking);
	return ROI_dst;
}
const vector<vector<Point>> getContours(const Mat & src, const Mat & dst) {
	//contours 함수 input이미지는 1채널이기에 src(BGR)이미지를 hsv로 변환 후  h(색상 성분)를 contours함수 inputImg로 입력. 
	Mat hsv;
	cvtColor(src, hsv, COLOR_BGR2HSV);
	vector<Mat>planes;
	split(hsv, planes);
	//imshow("H", planes[0]);

	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(planes[0], contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);

	//idx가 0 이상이면 루프 돌음
	for (int idx = 0; idx >= 0; idx = hierarchy[idx][0]) {  // [0] 은 next 정보
		drawContours(dst, contours, idx, Scalar(255, 255, 255), -1, LINE_8, hierarchy); // 사이즈 -1 통해 외곽선 내부 흰색으로 채움.
	}
	//imshow("ROI_dst", dst);
	return contours;
}
vector<Point> getVertex(vector<vector<Point>> contours_points, const Mat & src) {
	Mat dst;
	src.copyTo(dst);

	for (vector<Point>& pts : contours_points) { //  외곽선 점들이 내재된 contours의 외곽선들을 하나씩 스캐닝하여 .
		if (contourArea(pts) < 400) // 각 외곽선 면적 400 이상만 검출
			continue;  // 작은 크기의 외곽선 객체인 흰색객체가 나타날수 있기에 이런 오검을 방지하기 위해 조건문 세움.

		vector<Point> approx;
		approxPolyDP(pts, approx, arcLength(pts, true) * 0.02, true); // 각각의 외곽선을 근사화시킴.  3번쨰 인자 입실론 : 주어진 외곽선 전체 길이 * 0.02 형태로 입실론 값 지정함.
		// 각 도형의 꼭지점 좌표만으로 구성된 approx 벡터 포인터 타입 변수에 값들이 채워짐
		if (!isContourConvex(approx))
			continue;
		int vtc = (int)approx.size();  // 각 도형의 좌표들을 카운팅해 vtc 에 저장
		if (vtc == 4) { // 4개이면 사각형
			cout << "꼭짓점: " << endl;
			/* 핸드폰 기울기에 따라 인덱스 번호가 달라지는걸 통일 시킴.
				0, 1번째 인덱스 간
				x차이가 y차이보다 크면
				1 0
				2 3
			작으면
				0 3
				1 2 로  되며 작은 경우의 인덱스 순서를 따르고자 한다.
			*/
			if (abs(approx[0].y - approx[1].y) < abs(approx[0].x - approx[1].x)) { // 인덱스 통일
				swap(approx[0], approx[1]);
				swap(approx[1], approx[2]);
				swap(approx[2], approx[3]);
			}
			for (int x = 0; x < 4; ++x) {
				string str = format("idx : %d, y: %d,  x: %d ", x, approx[x].y, approx[x].x);
				cout << approx[x] << " ";
				putText(dst, str, approx[x], FONT_HERSHEY_SIMPLEX, 0.7, Scalar(0, 255, 255), 1, -1);
			}
			imshow("dst", dst);
			cout << endl;
			return approx;
		}
	}
	return vector<Point>(0);
}
void mergeAr(vector<Point>Vertex,  VideoCapture& Video, const Mat& CAM_ORI) {
	if (Vertex.size() == 4) { // 꼭짓점 4개 인 경우만(사각형)
		Mat Composite_Result; //  warpPerspective 함수에서 output 이미지로 사용할 예정.
		Mat Video_Frame;
		Video >> Video_Frame;
		Mat Mini_Frame;
		Video_Frame.copyTo(Mini_Frame);
		/*영상이 큼에도 resize 할 필요가없다. warpPerspective 함수 통해서 CAM_ORI 사이즈와 동일하게 출력하게 해줌,*/
		//Video_Frame 영상 크기 resize .
		//resize(Video_Frame, Mini_Frame, Size(abs(Vertex[2].x - Vertex[1].x)+15,abs( Vertex[1].y - Vertex[0].y) +1), 0, 0, 1); // Resize 기준은 Vertex 넓이 기준으로 조금 넓게. 빈틈이 없게 cover 하기 위함
		Point2f srcPts[4];
		Point2f dstPts[4];
		srcPts[0] = Point2f(0, 0);
		srcPts[1] = Point2f(0, Mini_Frame.rows);
		srcPts[2] = Point2f(Mini_Frame.cols, Mini_Frame.rows);
		srcPts[3] = Point2f(Mini_Frame.cols, 0);
		dstPts[0] = Point2f(Vertex[0]);
		dstPts[1] = Point2f(Vertex[1]);
		dstPts[2] = Point2f(Vertex[2]);
		dstPts[3] = Point2f(Vertex[3]);
		Mat per_mat = getPerspectiveTransform(srcPts, dstPts); //per_mat : srcPts 좌표를 dstPts 변환하는 변환행렬 리턴.
		warpPerspective(Mini_Frame, Composite_Result, per_mat, CAM_ORI.size()); //Composite_Result 이미지는 CAM_ORI 사이즈만큼 permat행렬 부분에 입력영상 Mini_Frame 을 넣어 warping함
		//imshow("Composite_Result", Composite_Result);

		Mat mask;
		inRange(CAM_ORI, Scalar(255, 255, 255), Scalar(255, 255, 255), mask);
		//imshow("thresholded image", mask);
		cvtColor(mask, mask, COLOR_GRAY2BGR); // 컬러영상에 합치기 위함.
		imshow("mask", mask);
		//imshow("Composite_Result", Composite_Result);
		bitwise_not(mask, mask);//white -> black
		imshow("mask22", mask);
		bitwise_and(CAM_ORI, mask, CAM_ORI); // CAM_ORI의 경우 Contour 내부 흰색으로 채웠음. 비트연산이기에 mask 행렬에 의해 0인 부분은 모두 0 (검정)으로 됨
		bitwise_or(CAM_ORI, Composite_Result, CAM_ORI); // warping했던 Composite_Result (영상) 과 CAM_ORI(마스크 부분 검정값 0) 합침.
	}
}

 

 

 

'프로그래머스 > OPENCV' 카테고리의 다른 글

opencv 완벽 삭제  (0) 2023.03.07
2주차 과제)subpixel 값으로 차선 엣지 좌표 검출하기  (0) 2022.12.11
참고  (0) 2022.12.09
OpenCV 외곽선 함수와 응용  (1) 2022.12.09
레이블링과 외곽선 검출  (0) 2022.12.08

+ Recent posts