본문 바로가기

프로그래밍 강좌/C++ - OpenCV

[OpenCV][C++] 훼손된 영상 복원하기 - cv::inpaint() 사진속 그림 지우기 제거 image restore 회복 되돌리기

이번에는 좀 재미있는 함수를 알아보려고 합니다.

훼손된 영상을 주변 픽셀 정보를 이용하여 채워넣는 방법입니다.

이 함수를 알아보기 위해 이전에 알아보았던 마우스 이벤트를 사용해볼께요.

https://m.blog.naver.com/dorergiverny/223084092098

 

[OpenCV][C++] 마우스 이벤트 처리 총정리 - onMouse setMouseCallback EVENT_MOUSEMOVE LBUTTONDOWN

이전에는 윈도우 생성 방법과 키보드 입력을 받아 이벤트를 처리하는 방법에 대해 알아보았습니다. https:/...

blog.naver.com

inpaint()의 원형은 아래와 같습니다.

src
입력 영상
inpaintMask
inpaint 할 마스크 영상
dst
출력 영상
inpaintRadius
inpainting 될 화소의 이웃 결정하는 반지름 크기
flags
inpainting 방법
(INPAINT_NS: Navier-Stokes 방법, INPAINT_TELEA: Alexandru Telea 방법)

 

1. 구조체 정의

마우스로 그려진 영상과 이 때 생성된 마스크 영상을 마우스 이벤트 핸들러에 인수로 전달하기 위한 구조체를 정의합니다. 그리고 마우스 그림을 그릴 때 사용될 전역 변수를 하나 설정합니다.

struct Data_t {
	cv::Mat image;
	cv::Mat mask;
};

cv::Point prevPt;
 

2. 마우스 이벤트 처리 함수 생성

왼쪽 버튼이 눌리면 그 때 좌표를 저장하고, 왼쪽 버튼이 눌린 채 마우스가 움직이면 이전 좌표와 line을 그리는 함수를 생성합니다. 이 때 display 하는 dst 영상과 mask 영상에 동시에 line을 그려야 한다.

void mouseEvent(int event, int x, int y, int flags, void* userData)
{
	Data_t* data = (Data_t*)userData;
	cv::Mat mask = data->mask;
	cv::Mat dst = data->image;
	switch (event) {
	case cv::EVENT_LBUTTONDOWN:
		prevPt = cv::Point(x, y);
		break;
	case cv::EVENT_MOUSEMOVE:
		if (flags & cv::EVENT_FLAG_LBUTTON) {
			cv::line(dst, prevPt, cv::Point(x, y), cv::Scalar(0, 255, 255), 2);
			cv::line(mask, prevPt, cv::Point(x, y), cv::Scalar(255), 3, -1);
			prevPt = cv::Point(x, y);
		}
	}
	cv::imshow("result", dst);
}
 

3. 원본 영상, 마스크 영상 생성

원본 영상을 읽어서 dst 영상에 저장을 하고(추후 영상을 복원해야 하기 때문에 clone()을 함) imshow()로 윈도우에 표시한다.

cv::Mat src = cv::imread("lena_color.bmp");
if (src.empty())  return 1;

cv::Mat dst = src.clone();
cv::Mat mask = cv::Mat::zeros(src.size(), CV_8UC1);
imshow("result", dst);
 

4. 콜백 함수 등록

마우스 이벤트를 동작시키기 위해 콜백 함수를 등록 한다.

Data_t data = { dst, mask };
cv::setMouseCallback("result", mouseEvent, (void*)&data);
 

5. 키보드 입력 이벤트

esc 키를 누르면 프로그램을 종료하고, 'r' 키를 누르면 원본 영상을 다시 로드하고 'i' 키를 누르면 inpaint를 수행하는 키보드 입력 이벤트를 생성합니다.

double inpaintRadius = 5;
int flags = cv::INPAINT_NS;	

bool bEscKey = false;
int nKey;
while (!bEscKey) {
	nKey = cv::waitKey(0);
	switch (nKey) {
	case 27:		// esc
		bEscKey = true;
		break;
	case 'r':		// restore
		mask = 0;
		src.copyTo(dst);
		imshow("result", dst);
		break;
	case 'i':		// inpaint
		cv::inpaint(dst, mask, dst, inpaintRadius, flags);
		imshow("result", dst);
		break;
	}
}
 

위 소스를 동작시켜 보면 아래와 같습니다.

일단 시작과 동시에 lena 영상이 뜹니다.

마우스를 result 윈도우에 올리고 왼쪽 버튼을 누르면서 마우스를 움직이면 영상이 훼손되기 시작합니다.

이 때 내부적으로 mask 영상이 아래와 같이 생성됩니다.

키보드의 'i' 버튼을 누르면 영상이 복원됩니다.

얼핏보면 원본 영상처럼 깔끔하게 복원된 것처럼 보이지만 자세히 보면 약간 미흡한 면이 있습니다.

전체 소스는 아래와 같습니다.

#include <iostream>
#include "opencv2/opencv.hpp"

struct Data_t {
	cv::Mat image;
	cv::Mat mask;
};

cv::Point prevPt;

void mouseEvent(int event, int x, int y, int flags, void* userData)
{
	Data_t* data = (Data_t*)userData;
	cv::Mat mask = data->mask;
	cv::Mat dst = data->image;
	switch (event) {
	case cv::EVENT_LBUTTONDOWN:
		prevPt = cv::Point(x, y);

		break;
	case cv::EVENT_MOUSEMOVE:
		if (flags & cv::EVENT_FLAG_LBUTTON) {
			cv::line(dst, prevPt, cv::Point(x, y), cv::Scalar(0, 255, 255), 3);
			cv::line(mask, prevPt, cv::Point(x, y), cv::Scalar(255), 3, -1);
			prevPt = cv::Point(x, y);
		}
	}
	imshow("result", dst);
}

int main()
{
	//cv::Mat src = cv::imread("Seoul.png");
	cv::Mat src = cv::imread("lena_color.bmp");
	if (src.empty())  return 1;

	cv::Mat dst = src.clone();
	cv::Mat mask = cv::Mat::zeros(src.size(), CV_8UC1);
	imshow("result", dst);

	Data_t data = { dst, mask };
	cv::setMouseCallback("result", mouseEvent, (void*)&data);

	double inpaintRadius = 5;
	int flags = cv::INPAINT_NS;	

	bool bEscKey = false;
	int nKey;
	while (!bEscKey) {
		nKey = cv::waitKey(0);
		switch (nKey) {
		case 27:		// esc
			bEscKey = true;
			break;
		case 'r':		// restore
			mask = 0;
			src.copyTo(dst);
			imshow("result", dst);
			break;
		case 'i':		// inpaint
			cv::inpaint(dst, mask, dst, inpaintRadius, flags);
			imshow("result", dst);
			break;
		}
	}

    return 0;
}
 

자, 그럼 inpaint 기술을 어느 경우에 사용할까요?

영상에 흠집이 생기거나 원하지 않는 부분 영역을 삭제하고 주변 화소값을 이용해서 채워 넣고 싶을 때 사용하면 됩니다.

서울의 옛날 사진을 가져왔어요. 1960년대 주한미군이 촬영한 영상이라네요.

출처:  https://blog.naver.com/aalsk7773/220659190711

오른쪽 상단의 전기줄을 지우고 싶네요.

지우고자 하는 전기줄에 선을 그립니다. (마스크를 생성하는 거죠)

이제 'i'를 누르면, 짜잔!~~

완벽하진 않지만 그럴싸하게 없어졌어요.

 

inpaint() 함수에서 가장 중요한 건 마스크 영상 입니다.

마스크 영상만 잘 만들게되면 그 마스크에 해당하는 부분을 지울 수 있어요.