▶ 영상 선명하게 만들기
사진을 찍다보면 살짝 흔들리거나 초점이 잘 안 맞아서 뿌연 영상이 찍힐 때가 종종 있다. 이런 영상을 선명하게 만드는 기법 중에 가장 간단한 것은 영상에 라플라시안 커널로 필터링해주는 것이다. 그러면 밝기값이 센 픽셀은 더 세게, 약한 픽셀은 더 약하게 해줌으로 결과적으로 엣지가 더 두드러지게 된다. cv::filter2D 함수를 이용하면 쉽게 필터링할 수 있지만, 좀 더 영상의 구조와 특징을 이해하기 위해서 책에서 소개한 방식으로 코딩했다.
1. 영상 읽기 및 띄우기
2. 선명화 된 영상 저장할 공간 마련 - create 메소드
3. 선명화 연산 - sharpen 함수
4. 선명화된 영상 띄우기
아래 코드를 한번 복붙해서 실행한 후에 코드에 대해 이해해가는 것을 추천한다.
#include <iostream>
#include <opencv2\highgui.hpp>
#include <opencv2\core.hpp>
void sharpen(const cv::Mat &image, cv::Mat &result);
int main()
{
cv::Mat before = cv::imread("robot.jpg");
cv::imshow("Before", before);
cv::Mat after;
after.create(before.rows, before.cols, before.type());
sharpen(before, after);
cv::imshow("After", after);
cv::waitKey(0);
return 0;
}
void sharpen(const cv::Mat &image, cv::Mat &result)
{
int nchannels = image.channels(); // 채널 개수 얻기, 컬러 영상이므로 여기서는 3.
// 모든 행 대상 (처음과 마지막 제외)
for (int j = 1; j < image.rows - 1; j++)
{
const uchar* previous = image.ptr<const uchar>(j - 1); // 이전 행 주소
const uchar* current = image.ptr<const uchar>(j); // 현재 행 주소
const uchar* next = image.ptr<const uchar>(j + 1); // 다음 행 주소
uchar* output = result.ptr<uchar>(j); // 결과 이미지의 현재 행 주소
for (int i = nchannels; i < (image.cols - 1)*nchannels ; i++) // int i = 3; i < 397*3; i++, 왜 3부터 시작할까? 그리고 왜 397*3-1까지만 할까? 아래 설명 참고.
{
// 선명화 연산자 적용
*output++ = cv::saturate_cast<uchar>(5 * current[i] - current[i - nchannels] - current[i + nchannels] - previous[i] - next[i]); // *output++의 의미는? saturate_cast의 역할은? 아래 설명 참고.
}
}
//처리하지 않는 화소를 검정색으로 설정
result.row(0).setTo(cv::Scalar(0, 0, 0));
result.row(result.rows - 1).setTo(cv::Scalar(0, 0, 0));
result.col(0).setTo(cv::Scalar(0, 0, 0));
result.col(result.cols - 1).setTo(cv::Scalar(0, 0, 0));
}
원본 영상과 결과 영상은 아래와 같다.
원본 및 결과 이미지
원본 이미지와 결과 이미지를 비교해보면 한결 선명해진 것을 확인할 수 있다.
▶ 좀 더 알고 넘어갈 것들
1) sharpen 함수
정의한 sharpen 함수에 대해 설명하겠다. 일단 첫번째 for 문을 보면, 첫번째 행과 마지막 행을 제외하고 작업한다. 왜냐하면 윗 행과 아래 행이 존재할 때만 선명화 작업을 해줄 수 있기 떄문이다(이 방식으로는). 그리고 두번째 for문은 열과 관련된 것인데, 첫번째 열과 마지막 열을 제외하고 작업한다. 마찬가지로 왼쪽 열과 오른쪽 열이 존재할 때만 선명화 작업을 해줄 수 있기 때문이다.
for (int i = nchannels; i < (image.cols - 1)*nchannels ; i++)
이 부분은 좀 더 설명이 필요할 것 같다. nchannels 부터 (image.cols-1)*nchannels - 1까지 반복한다는 것인데, 여기서 nchannels는 3이고, image.cols는 398이므로 다시 적으면, 3부터 397*3-1까지 반복한다는 것이다. 왜 3부터일까? 그 이유는 첫번째 열에 b, g, r 세 개의 픽셀값들이 0번째, 1번째, 2번째 요소에 존재하기 떄문이다. 397*3 -1 까지인 이유는 마지막 열의 b, g, r 세 개의 픽셀값들을 제외하기 위함이다.
이렇게 연산을 해줄 범위를 설정한 후에 선명화 연산자를 적용한다.
*output++ = cv::saturate_cast<uchar>(5 * current[i] - current[i - nchannels] - current[i + nchannels] - previous[i] - next[i]);
일단 cv::saturate_cast<uchar>는 허용된 화소값의 범위를 벗어나지 않게 해주는 친구다. 만약 255 이상이면 255가 되게, 0이하면 0이 되게 해줌으로 허용된 화소값 내에 머무르게 한다.
5*current[i] - current[i - nchannels] - current[i + nchnnels] - previous[i] - next[i]가 선명화 작업의 핵심인데 일단 현재 픽셀의 값에 5를 곱한 후 상하좌우에 있는 픽셀의 값들을 빼준다. 만약 5개 픽셀의 값이 모두 동일하다면 아무 변화가 없을 것이다. 반면 현재 픽셀의 값이 주변 픽셀의 값보다 크다면 그것은 더 부각될 것이고, 작다면 더 작아질 것이다. 결과적으로 엣지가 부각된다.
*output++에 대해서 마지막으로 설명하겠다. output에는 결과 이미지의 현재 행의 주소가 저장되어 있는데, *연산자로 그 주소 안에 있는 값을 불러온다. 현재 행의 첫번째 열의 b 채널값을 불러올 것이다. 그리고 ++연산자로 인해 루프가 한번 돌았을 때는 첫번째 열의 g 채널값을 불러올 것이다. 이런 식으로 하나하나 접근해서 선명화 작업 결과를 저장한다.
2) row 메소드와 col 메소드, 그리고 setTo 메소드
row 메소드는 하나의 행을, col 메소드는 하나의 열을 관심영역으로 지정한다.
result.row(0).setTo(cv::Scalar(0, 0, 0));
위 코드는 result 영상의 0번째 행은 검정색으로 설정하겠다는 것이다. setTo 메소드는 행렬의 모든 요소에 설정해준 값을 할당한다.
<참고자료>
[1] 로버트 라가니에 지음, 이문호 옮김, "OpenCV를 활용한 컴퓨터 비전 프로그래밍 3/e", 에이콘
'Dev > C, C++' 카테고리의 다른 글
[opencv와 C++로 컴퓨터 비전] 영상 재매핑 처리 (0) | 2018.03.21 |
---|---|
[opencv와 C++로 컴퓨터 비전] 두 개의 영상 가중합하기 (0) | 2018.03.19 |
[opencv와 C++로 컴퓨터 비전] 영상 내 컬러 개수 감소 (0) | 2018.02.15 |
제가 심심풀이로 만든 저격수 훈련 게임: '아임스나이퍼' (은근 중독성 있음) (18) | 2018.02.06 |
[opencv와 C++로 컴퓨터 비전] 예쁜 타이머 만들기 (타이머 종료시 음악 재생) (6) | 2018.02.02 |
[opencv와 C++로 컴퓨터 비전] 영상에 소금-후추 잡음(salt-and-pepper noise) 넣기 (2) | 2018.02.01 |
[opencv와 C++로 컴퓨터 비전] 영상 위에 로고 넣기 (0) | 2018.01.31 |
[opencv와 C++로 컴퓨터 비전] 영상 위에 그림 그리기 및 글쓰기 (0) | 2018.01.30 |