[w6d1] Filters

35342 단어 opencvopencv

(Ubuntu 18.04.6 LTS)
2022.03.21.
C++, VS code 사용
프로그래머스 자율주행 데브코스 3기

Mean filter

영상의 한 점의 값을 주변 픽셀들과의 산술 평균값을 사용하는 방식으로 영상을 부드럽게 처리하여 노이즈의 효과를 적게한다.
https://en.wikipedia.org/wiki/Geometric_mean_filter#/media/File:Gmf.jpg

OpenCV에서 제공하는 mean filter는 blur의 형태로 주어진다.

void cv::blur 	( 	InputArray  	src,
		OutputArray  	dst,
		Size  	ksize,
		Point  	anchor = Point(-1,-1),
		int  	borderType = BORDER_DEFAULT 
	) 		

https://docs.opencv.org/4.x/d4/d86/group__imgproc__filter.html#ga8c45db9afe636703801b0b2e440fce37

Size는 커널의 사이즈로, cv::Size(n,n)을 인자로 주면 n x n 커널을 적용하게된다.

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

int main()
{
    cv::Mat src = cv::imread("./resources/lenna.bmp",cv::IMREAD_COLOR);

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

    cv::Mat dst;
    cv::Mat Planes[5];
    for (int i=0;i<5;i++){
        cv::String str = cv::format("Mean filter : %d x %d",2*i+3,2*i+3);
        cv::String title = cv::format("Mean filter : %d x %d",2*i+3,2*i+3);
        cv::blur(src,dst,cv::Size(2*i+3,2*i+3));
        Planes[i]=dst.clone();
        cv::putText(Planes[i],str,cv::Point(0,30),cv::FONT_HERSHEY_DUPLEX,0.7,cv::Scalar(255,0,0),1.7);
        cv::imshow(title,Planes[i]);
    }

    cv::imshow("src",src);
    while (cv::waitKey()!=27) {
        continue;
    }
    cv::destroyAllWindows();
    return 0;
}

Mean filter의 커널 사이즈에 따른 효과는 위 코드의 결과로 확인해 볼 수 있다. 커널의 사이즈가 클 수록 blur가 많이 되는 것을 확인할 수 있었다.

2D filter의 연산양은 다소 많은데, Separable filter일 경우에는 1D filter 두개로 나누어 계산할 수 있으며 시간복잡도가 크게 감소한다. N x M 크기의 이미지에서 n x m 크기의 filter를 이용해 연산을 수행할 경우 2D filter는 O(M·N·m·n)임에 비해 1D filter를 두번 적용할 경우 O(M·n·(m+n))의 시간 복잡도를 가진다.

위의 예시에서는 왼쪽의 1D filter를 이용할 경우 곱셈 3번, 덧셈 2번을 필터마다 수행해 총 곱셈 6번, 덧셈 4번임에 비해 오른쪽은 곱셈 9번, 덧셈 8번의 연산을 해야한다.
https://en.wikipedia.org/wiki/Separable_filter

Gaussain filter

평균값 필터에 의한 블러 처리는 거리와 관계 없이 적용되는 필터가 모두 같은 가중치를 가지므로 멀리 있는 픽셀의 영향이 비교적 강하다. 먼 거리의 픽셀의 영향을 줄이는 데에는 가우시안 필터가 사용될 수 있다.

void cv::GaussianBlur 	( 	InputArray  	src,
		OutputArray  	dst,
		Size  	ksize,
		double  	sigmaX,
		double  	sigmaY = 0,
		int  	borderType = BORDER_DEFAULT 
	) 		

가우시안 필터는 표준 정규 분포를 기준으로 만들어졌으며, 픽셀 1개 거리를 1σ로 두고 만들어진다. ksize를 cv::Size()로 둘 경우 뒤의 sigma 값에 따라서 만들어진다. 영상이 gray scale image일 경우 6σ+1, true color image일 경우 8σ+1의 크기로 만들어진다. sigmaY를 디폴트값인 0으로 둘 경우 sigmaX와 같은 값을 가지게 된다.

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

int main()
{
    cv::Mat src = cv::imread("./resources/lenna.bmp",cv::IMREAD_COLOR);

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

    cv::Mat dst;
    cv::Mat Planes[5];
    for (int i=0;i<5;i++){
        cv::String str = cv::format("Gaussian filter : %d",i+1);
        cv::String title = cv::format("Gaussian filter : %d",i+1);
        cv::GaussianBlur(src,dst,cv::Size(),i+1);
        Planes[i]=dst.clone();
        cv::putText(Planes[i],str,cv::Point(0,30),cv::FONT_HERSHEY_DUPLEX,0.7,cv::Scalar(255,0,0),1.7);
        cv::imshow(title,Planes[i]);
    }

    cv::imshow("src",src);
    while (cv::waitKey()!=27) {
        continue;
    }
    cv::destroyAllWindows();
    return 0;
    return 0;
}


GaussianBlur는 InputArray와 OutputArray의 데이터 depth가 동일하기 때문에 소수점을 이용하기 위해서는 src의 데이터 형식을 CV_32F와 같은 형태로 지정해주어야 한다.

Mean filter와 Gaussian filter를 비교하였을 때, mean filter는 커널의 크기를 크게 하여도 세밀한 선 이미지에서 두꺼워지기만 하는 현상을 볼 수 있다. 자연스러운 blur 처리를 위해서는 연산양이 더 많긴 하지만 Gaussian filter를 이용하는 것이 좋다. Gaussian filter 또한 mean filter와 마찬가지로 separable filter로 1D Gaussian filter를 두 번 적용하는 방식으로 이용한다.

Median filter

Median filter도 노이즈 현상 제거 등을 위해 사용되는 필터로 필터 내 값들 중에서 중간 값을 사용하는 방식이다. Salt and pepper noise는 센서의 발열이나 전기적 신호 등의 강한 자극 신호가 원인이 되어 나타나는 노이즈로, 매우 밝거나 어두운 값을 가지게 되는데, median filter를 이용해 효과적으로 제거할 수 있다.

https://en.wikipedia.org/wiki/Salt-and-pepper_noise

void cv::medianBlur 	( 	InputArray  	src,
		OutputArray  	dst,
		int  	ksize 
	) 		

ksize에는 커널 사이즈를 int type으로 입력한다.

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

int main()
{
    cv::Mat src = cv::imread("./resources/Noise_salt_and_pepper.png",cv::IMREAD_GRAYSCALE);

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

    cv::Mat dst;
    cv::medianBlur(src,dst,3);
    cv::imshow("src",src);
    cv::imshow("dst",dst);
    while (cv::waitKey()!=27){
        continue;
    }
    cv::destroyAllWindows();
    return 0;
}

Unsharp masking

Unsharp masking은 blur 처리를 한 unsharp mask를 invert, scale해 원본 영상에 더해주는 방식으로 edge 부분이 날카로워지는 특징을 가진다.
https://en.wikipedia.org/wiki/Unsharp_masking

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

int main()
{
    cv::Mat src = cv::imread("./resources/lenna.bmp",cv::IMREAD_COLOR);
    cv::Mat blr, sr_blr;
    cv::blur(src,blr,cv::Size(5,5));
    sr_blr=src-blr;
    
    cv::imshow("src",src);
    cv::imshow("blur",blr);
    cv::imshow("src-blur",sr_blr);
    cv::imshow("src+(src-blur)",src+sr_blr);
    cv::imshow("src+2*(src-blur)",src+2*sr_blr);
    while (cv::waitKey()!=27) continue;
    cv::destroyAllWindows();
    return 0;
}

코드의 모습은 아래와 같다.

src에서 blur처리를 하는 경우 밝기 변화가 큰 부분은 주변부와 값의 차이가 큰 모서리 등의 고주파 영역이다. 따라서 src에서 blur를 빼주는 경우 해당 영역만 남는 것을 src-blur 창에서 확인할 수 있다. src - blur를 적당히 scaling해 더해주면 이러한 영역의 강화효과를 볼 수 있다.

Bilateral filter

이전의 가우시안 필터 등을 사용할 경우 밝기 영역이 바뀌는 경계면의 구분이 흐릿해지는 것을 볼 수 있었다. 경계면을 유지하면서 노이즈를 제거하는 방식으로 Bilateral filter가 있으며 아래와 같은 식으로 표현된다.

https://en.wikipedia.org/wiki/Bilateral_filter

기존의 가우시안 필터에 밝기 차이에 대한 정규분포 텀이 추가된 형태로, 밝기 차이가 많이 나는 경우 해당 부분의 필터값이 0에 가깝게 되어 이러한 문제를 해결했다.

void cv::bilateralFilter 	( 	InputArray  	src,
		OutputArray  	dst,
		int  	d,
		double  	sigmaColor,
		double  	sigmaSpace,
		int  	borderType = BORDER_DEFAULT 
	) 	

d는 필터링에 사용될 픽셀의 지름으로 -1을 입력하면 sigmaSpace 값에 따라서 자동으로 결정된다. sigmaColor는 밝기 영역에서 표준편차를 결정하는 것으로 3 sigma가 대략적으로 99.7%임을 고려하면 밝기 차가 입력값의 3배 이상인 경우 영향이 거의 없다. sigmaSpace는 Gaussian filter와 동일하다.

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

int main()
{
    cv::Mat src = cv::imread("./resources/lenna.bmp",cv::IMREAD_GRAYSCALE);

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

    cv::Mat bilateral,Gaussian;
    cv::bilateralFilter(src,bilateral,-1,20,5);
    cv::GaussianBlur(src,Gaussian,cv::Size(),5);
    cv::imshow("bilateral",bilateral);
    cv::imshow("Gaussian",Gaussian);
    cv::imshow("src",src);
    while (cv::waitKey()!=27) continue;
    cv::destroyAllWindows();
    return 0;
}

적용 결과를 보면, Gaussian과 비교하였을 때 edge 부분이 잘 유지되면서도 blur처리가 잘 된것을 확인할 수 있다.

좋은 웹페이지 즐겨찾기