본문 바로가기

IT/OPENCV

[OPENCV] 6. 이미지 프로세싱(2) - 샤프닝 & 가장자리 검출

반응형

☞ 메인보드 : Jetson Nano Developer Kit
운영 체제 : Ubuntu 18.04 - JetPack 4.4.1
☞ IDE : Visual Studio Code
☞ 언어 : C++
 

 
 
 


목차

1. Sobel Mask
2. Laplacian
3. Canny

 
 
 


 
 
 
 
 
 

Sobel Mask

카메라의 픽셀 관점에서 가장자리는 픽셀의 변화가 급격하게 일어나는 부분을 의미한다. 변화율은 미분을 이용하여 구할 수 있고, 연속적인 픽셀 데이터를 각각 x, y축으로 편미분하고 얻은 값으로 기울기 벡터의 크기와 방향을 나타낼 수 있다. Sobel Mask는 x, y축 편미분으로 얻은 벡터의 크기를 기반으로 필터 마스크를 이용하여 노이즈를 제거하면서 가장자리 검출의 효율을 높인다.
 
 
 

void Sobel( InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize = 3, double scale = 1, double delta = 0, int borderType = BORDER_DEFAULT );

 
 

※ Scharr Filter

Sobel Mask는 계산할 픽셀과 거리가 먼 픽셀은 연관성이 떨어지고 미분의 근사치로 가장자리를 검출하기 때문에 빠르지만 부정확한 값이 나올 수 있다. Scharr Filter는 이러한 Sobel Mask의 문제점을 개선하기 위해 나왔다. 방향에 대한 정확성이 떨어지는 Sobel Mask와 달리 크기와 방향을 모두 고려하여 가장자리 검출에 대한 정확도를 높였다.
 
Scharr 함수를 직접 사용할 수도 있고, Sobel 함수의 ksize 파라미터에 -1 을 입력하거나 FILTER_SCHARR를 입력하면 Scharr Filter를 사용할 수 있다
 
 

void Scharr( InputArray src, OutputArray dst, int ddepth, int dx, int dy, double scale = 1, double delta = 0, int borderType = BORDER_DEFAULT );

 
 
 

 소스 코드

 

#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>

int main(){
    cv::Mat src;
    int kernel_size = 3;
    int scale = 1;
    int delta = 0;
    int ddepth = CV_32F;
    
    src = cv::imread("road2.jpg", cv::IMREAD_COLOR);
    cv::resize(src, src, cv::Size(src.cols/2, src.rows/2));
    if(src.empty()){
        std::cout << "Can't Open Image" << std::endl;
        return -1;
    }

    cv::Mat blur, gray;
    cv::bilateralFilter(src, blur, kernel_size, 100, 100); 
    cv::cvtColor(blur, gray, cv::COLOR_BGR2GRAY);
    
    // Sobel Mask
    cv::Mat sobel_x, sobel_y, scharr_x, scharr_y;
    cv::Sobel(gray, sobel_x, ddepth, 1, 0);
    cv::Sobel(gray, sobel_y, ddepth, 0, 1);
    
    // Scharr Filter
    cv::Sobel(gray, scharr_x, cv::FILTER_SCHARR, 1, 0);
    cv::Sobel(gray, scharr_y, cv::FILTER_SCHARR, 0, 1);
    
    cv::imshow("sobel x", sobel_x);
    cv::imshow("sobel y", sobel_y);
    cv::imshow("scharr x", scharr_x);
    cv::imshow("scharr y", scharr_y);
    cv::waitKey(0);

    return 0;
}

 
 
 
 
 

※ 실행 결과

 

Sobel X(왼쪽), 원본(중앙) Sobel Y(오른쪽)
Scharr X(왼쪽), Scharr Y(오른쪽)

 
 
 
 
 


Laplacian

필터링 기법에는 블러링, 샤프닝, 노이즈 제거가 있다. Laplacian은 이미지를 날카롭게 만드는 샤프닝 기법 중 하나이다. 이 기법 또한 가장자리 검출을 위해 사용되는데 1차 미분 근사값을 이용한 Sobel Mask와 달리 2차 미분 연산에 의해 나오는 커널 값(변화율)은 중앙 픽셀의 4 또는 8 방향의 근접 픽셀과 연관하여 가장자리를 검출해낸다.
 
 
 

void Laplacian( InputArray src, OutputArray dst, int ddepth, int ksize = 1, double scale = 1, double delta = 0,  int borderType = BORDER_DEFAULT );

 
 

소스 코드

 

#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>

int main(){
    cv::Mat src;
    int kernel_size = 3;
    int scale = 1;
    int delta = 0;
    int ddepth = CV_16S;
    
    src = cv::imread("road2.jpg", cv::IMREAD_COLOR);
    cv::resize(src, src, cv::Size(src.cols/2, src.rows/2));
    if(src.empty()){
        std::cout << "Can't Open Image" << std::endl;
        return -1;
    }

    cv::Mat blur, gray, dst, abs_dst;
    cv::GaussianBlur(src, blur, cv::Size(3,3), 10);
    cv::cvtColor(blur, gray, cv::COLOR_BGR2GRAY);
    
    cv::Mat dst, abs_dst;
    cv::Laplacian(gray, dst, ddepth, kernel_size, scale, delta, cv::BORDER_DEFAULT);
    cv::convertScaleAbs(dst, abs_dst);

    cv::imshow("reult", abs_dst);
    cv::waitKey(0);

    return 0;
}

 
 
 
 
 

※ 실행 결과

 
 
 

 
 

 


③ Canny

앞서 알아본 Sobel Mask의 검출 과정이 1차 미분 연산에 의한 기울기 벡터의 크기로 가장자리를 검출했다면 Canny는 크기뿐만 아니라 방향도 함께 고려하는 엄격한 기법이다. 1986년 John F. Canny에 의해 개발된 Canny 엣지 검출기 Gaussian Blur를 이용하여 이미지 내에 존재하는 노이즈를 제거하고 이중 임계값에 해당하는 엣지만을 검출한다.
 
Canny 알고리즘 개발에 있어 John F. Canny는 가장자리 감지 기준을 충족하기 위해 노이즈 제거에 많은 신경을 썼다. 하한 임계값, 상한 임계값 두 임계값을 고려하여 해당되지 않는 가장자리를 모두 노이즈 처리하고 확실한 가장자리만을 남긴다. 이러한 엄격한 감지 기준으로 인한 높은 정확성 때문에 대중에게 가장 사랑받고 있는 가장자리 검출 기법으로 알려져있다. 
 
 

☞ 가장자리 감지 기준

 
◎ 낮은 오류율
◎ 중앙의 가장자리 포인트
◎ 가장자리 한 번만 표시
 
 

☞ 가장자리 감지 과정
 

◎ 블러링
◎ 기울기 벡터(그래디언트) 계산
◎ 이중 임계값을 설정하고 노이즈 제거
◎ 임시 가장자리 설정
◎ 모멘텀에 근거한 가장자리 설정 마무리
 
 
 

void Canny( InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize = 3, bool L2gradient = false );
void Canny( InputArray dx, InputArray dy, OutputArray edges, double threshold1, double threshold2, bool L2gradient = false );

 
 

 소스 코드

 

#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>

int main(){
    cv::Mat src;
    int kernel_size = 3;
    int ddepth = CV_16S;
    int low_threshold = 150;
    int high_threshold = low_threshold * 2;
    
    src = cv::imread("road2.jpg", cv::IMREAD_COLOR);
    cv::resize(src, src, cv::Size(src.cols/2, src.rows/2));
    if(src.empty()){
        std::cout << "Can't Open Image" << std::endl;
        return -1;
    }

    cv::Mat blur, gray;
    cv::bilateralFilter(src, blur, kernel_size, 100, 100); 
    cv::cvtColor(blur, gray, cv::COLOR_BGR2GRAY);
    
    cv::Mat scharr_x, scharr_y;
    cv::Scharr(gray,scharr_x,ddepth,1,0);
    cv::Scharr(gray,scharr_y,ddepth,0,1);
    
    cv::Mat scharrToCanny, grayToCanny;
    cv::Canny(scharr_x, scharr_y, scharrToCanny, 500, 1000);
    cv::Canny(gray, grayToCanny, low_threshold, high_threshold);
    
    cv::imshow("Canny", scharrToCanny);
    cv::imshow("gray canny",grayToCanny);
    cv::waitKey(0);

    return 0;
}

 
 
 
 

※ 실행 결과

 
 

Canny threshold 150(왼쪽), 원본(중앙), Canny threshold 500(오른쪽)

 
가장 왼쪽에 있는 이미지는 Scharr Filter를 이용하여 x, y 연산을 거친 후 Canny를 적용한 것이고, 오른쪽에 있는 이미지는 GrayScale 에서 아무 Filter를 거치지 않고 바로 Canny를 적용한 이미지이다. 두 이미지 모두 low threshold * 2 = high threshold 로 계산했는데, Scharr Filter의 threshold가 500으로 높은 이유는 100이나 200 처럼 낮은 수로 임계값을 설정하면 너무 많은 가장자리를 인식하기 때문이다. 과정도 다르고 사용한 함수 파라미터도 다르다. 아래에 각 이미지에 적용한 Canny 함수를 첨부할테니 참고하면 좋겠다.
 
 
더보기를 눌러주세요.

더보기

☞ 왼쪽 이미지

 

 

 

 

 

☞ 오른쪽 이미지

 

 

 
 
 
 
 

반응형