이번에는 좀 재미있는 함수를 알아보려고 합니다.
훼손된 영상을 주변 픽셀 정보를 이용하여 채워넣는 방법입니다.
이 함수를 알아보기 위해 이전에 알아보았던 마우스 이벤트를 사용해볼께요.
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년대 주한미군이 촬영한 영상이라네요.
오른쪽 상단의 전기줄을 지우고 싶네요.
지우고자 하는 전기줄에 선을 그립니다. (마스크를 생성하는 거죠)
이제 'i'를 누르면, 짜잔!~~
완벽하진 않지만 그럴싸하게 없어졌어요.
inpaint() 함수에서 가장 중요한 건 마스크 영상 입니다.
마스크 영상만 잘 만들게되면 그 마스크에 해당하는 부분을 지울 수 있어요.