Linear Fileter(선형 필터링)
마스크를 주어진 영상 위에서 이동하면서 처리하는 방식이다.
Filter는 마스크와 함수를 결합한 것으로, 마스크는 일반적으로 양변의 길이가 모두 홀수인 직사각형 행렬을 말한다.
즉 마스크를 이용해서 영상을 덮어서 영상의 일부분을 한정해서 영역을 산정할 수 있는 어떠한 영상을 의미함!
이렇게 마스크와 함수가 결합한 형태를 필터(Filter)라고 하고,
필터링 과정에서 마스크가 사용된다.
Mat myCopy(Mat srcImg) {
int width = srcImg.cols;
int height = srcImg.rows;
Mat dstImg(srcImg.size(), CV_8UC1); //입력한 영상과 동일한 크기의 Mat 생성
uchar* srcData = srcImg.data; // Mat객체의 data를 가리키는 포인터
uchar* dstData = dstImg.data;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
dstData[y * width + x] = srcData[y * width + x];
} // 화솟값을 일일이 읽어 다른 배열에 저장
}
return dstImg;
}
화소 기반 접근법을 사용해서 Mat 객체의 정보를 dstImg Mat 객체에 담고 있다.
가로와 세로를 순회하는 이중 for문 안에서 화솟값을 일일이 읽어와 다른 배열에 저장
int myKernelConv3x3(uchar* arr, int kernel[][3], int x, int y, int width, int height) {
int sum = 0;
int sumKernel = 0;
// 특정 화소의 모든 이웃 화소에 대해 반복
for (int j = -1; j <= 1; j++) {
for (int i = -1; i <= 1; i++) {
if ((y + j) >= 0 && (y + j) < height && (x + i) >= 0 && (x + i) < width) {
// 영상 가장자리에서 영상 밖의 화소를 읽지 않도록 하는 조건문
sum += arr[(y + j) * width + (x + i)] * kernel[i + 1][j + 1];
sumKernel += kernel[i + 1][j + 1];
}
}
}
if (sumKernel != 0) { return sum / sumKernel; } // 합이 1로 정규화되도록 영상의 밝기변화 방지
else return sum;
}
선형 필터링에서 마스크 기반 처리를 위해, 마스크에 대한 Convolution을 수행한다.
Convolution 연산의 과정은 다음과 같다.
(1) 마스크를 입력 영상의 해당 위치에 덮는다.
(2) 각 입력 화솟값과 대응되는 Mask 요소의 값을 곱셈
(3) 곱셈 결과를 합산
Convolution의 연산의 계산량은 다음과 같다.
영상의 크기 * 마스크의 크기 = O(M * N * m * n)
Linear Filter의 대표적인 종류는 바로 Gaussian 필터이다.
가우시안 분포 형태로 마스크가 주어져있고, 현재 화소와 가까울 수록 가중치를 부여한다.
Blur 효과가 나타나기 때문에 잡음 제거에 탁월, 표준편차에 의해 좌우된다.
Mat myGaussianFilter(Mat srcImg) {
int width = srcImg.cols;
int height = srcImg.rows;
int kernel[3][3] = { 1,2,1, // 3by3 형태의 Gaussian 마스크 배열
2,4,2,
1,2,1 };
Mat dstImg(srcImg.size(), CV_8UC1);
uchar* srcData = srcImg.data;
uchar* dstData = dstImg.data;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
dstData[y * width + x] = myKernelConv3x3(srcData, kernel, x, y, width, height);
} // 앞서 구현한 convolution에 마스크 배열을 입력해 사용
}
return dstImg;
}
Sobel 필터
영상의 경계를 뚜렷하게 만드는 필터링. (Sharpening)
대표적인 에지 검출 필터, 영상의 특정 방향에 대해 미분하는 성격을 가진 마스크를 사용한다.
가로 및 세로 방향에 대한 에지 검출을 별도로 수행, 절댓값 합 형태로 합성해 최종적으로 에지를 구한다.
Mat mySobelFilter(Mat srcImg) {
int kernelX[3][3] = {
-1, 0, 1,
-2, 0, 2,
-1, 0, 1 }; // 가로방향 sobel 마스크
int kernelY[3][3] = {
-1, -2, -1,
0, 0, 0,
1, 2, 1 }; // 세로방향 sobel 마스크
Mat dstImg(srcImg.size(), CV_8UC1);
// 마스크의 합이 0이 되므로 1로 정규화하는 과정은 필요 없음
uchar* srcData = srcImg.data;
uchar* dstData = dstImg.data;
int width = srcImg.cols;
int height = srcImg.rows;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
dstData[y * width + x] = (abs(myKernelConv3x3(srcData, kernelX, x, y, width, height)) + abs(myKernelConv3x3(srcData, kernelY, x, y, width, height))) / 2;
} // 두 에지 결과의 절댓값 합 형태로 최종결과 도출
}
return dstImg;
}
이렇게 커널을 생성해서, 데이터 배열을 추출해 앞서 구현한 Convolution에 마스크를 입력한다.
Image Pyramid
여러 스케일에 걸쳐서 분석하는 방법.
입력 이미지의 크기를 단계적으로 변화, 축소 시켜 필요한 분석 작업을 수행한다.
이렇게 생기게 된 일련의 이미지 집합을 이미지 피라미드라고 한다.
Gaussian Pyramid
Gaussian pyramid로 인해, 영상에 대한 down sampling(또는 Sub sampling)과 Gaussian 필터 기반 smoothing을 조합해, 점차 작은 해상도의 영상을 반복적으로 생성한다.
down sampling : 화소를 홀수, 혹은 짝수 칸만 남겨서 작은 영상을 생성하는 것
vector<Mat> myGaussianPyramid(Mat srcImg) {
vector<Mat> vec; // 여러 영상을 모아서 저장하기 위해 STL의 vector 컨테이너 사용
vec.push_back(srcImg);
for (int i = 0; i < 4; i++) {
srcImg = mySampling(srcImg); // 앞서 구현한 down sampling
srcImg = myGaussianFilter(srcImg); // 앞서 구현한 Gaussian filtering
vec.push_back(srcImg); // vector 컨테이너에 처리 결과 하나씩 삽입
}
return vec;
}
코드를 보면,
원본 이미지를 먼저 저장하고, myGaussianFilter에 의해 Processing된 Img를 여러번 벡터에 저장하고 있다.
Laplacian Pyramid
Gaussian Pyramid와 유사하지만, 높은 해상도와 영상과 작아진 영상간의 차 영상을 저장
제일 처음에 가장 작은 원본 크기의 원본 영상이 들어가게 되고,
이를 upSampling해서 높은 해상도의 영상을 복원할 수 있게된다.
vector<Mat> myLaplacianPyramind(Mat srcImg) {
vector<Mat> vec;
for (int i = 0; i < 4; i++) {
if (i != 3) {
Mat highImg = srcImg; // 수행하기 이전 영상을 확대
srcImg = mySampling(srcImg);
srcImg = myGaussianFilter(srcImg);
Mat lowImg = srcImg;
resize(lowImg, lowImg, highImg.size());
// 작아진 영상을 백업한 영상의 크기로 확대
vec.push_back(highImg - lowImg + 128);
// 차 영상을 컨테이너에서 삽입
// 128을 더해준 것은 차 영상에서 오버플로우를 방지하기 위함이다.
}
else {
vec.push_back(srcImg);
}
}
return vec;
}
마지막 인덱스인 경우만 제외하고 벡터에 차 영상을 삽입해준다.
void ex5() { // 라플라시안 피라미드 테스트
Mat src_img = imread("gear.jpg", 0);
Mat dst_img;
vector<Mat> vecLap = myLaplacianPyramind(src_img);
reverse(vecLap.begin(), vecLap.end());
for (int i = 0; i < vecLap.size(); i++) {
if (i == 0) {
dst_img = vecLap[i];
}
else {
resize(dst_img, dst_img, vecLap[i].size());
dst_img = dst_img + vecLap[i] - 128;
}
string fname = "ex5_lap_pyr" + to_string(i) + ".png";
imwrite(fname, dst_img);
imshow("EX5", dst_img);
waitKey(0);
destroyWindow("EX5");
}
}
위 코드는 라플라시안 피라미드를 확보하기 위해 벡터의 순서를 반대로 처리하는 함수이다.
위의 코드와는 반대로 처음 인덱스에서가장 작은 영상은 차영상이 아니기 때문에 바로 불러와주고,
그 이외의 경우에서는 작은 영상을 UpSampling하며 순차적으로 불러온다.
여기서는 오버플로우를 방지하기 위해 128을 빼준다.
'Major Study > Digital Image Processing' 카테고리의 다른 글
Lecture 11. Image Stitching (Panorama) (1) | 2022.06.09 |
---|---|
Lecture10. Image Transform (이미지 변환) (0) | 2022.06.09 |
Lecture9 : Local Feature Detection and Matching (0) | 2022.06.09 |
Lecture8 : Clustering and Segmentation (군집화와 영역화) (0) | 2022.06.03 |
주파수 영역 필터링(Filtering in Frequency Domain) (0) | 2022.04.05 |