1. 그래디언트

1) 함수 f(x, y)를 x축과  y축으로 각각 편미분(partial derivative)하여 벡터 형태로 표현한 것.

del f = [f_x  =  f_x i + f_y j

            f_y]

 

2) 그래디언트 크기  : L2 Norm

3) 그래디언트 방향 : t = tan^-1 (f_y/f_x)

*arctan : tan의 역함수

4) 실제 영상에서 구한 그래디언트 크기와 방향.

  - 그래디언트 크기: 픽셀값의 차이 정도, 변화량

  - 그래디언트 방향: 픽셀값이 가장 급격하게 증가하는 방향.

 

 

 

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

using namespace std;
using namespace cv;

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

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

	Mat dx = Mat::zeros(src.rows, src.cols, CV_8UC1); // Gray scale 형식
	Mat dy = Mat::zeros(src.rows, src.cols, CV_8UC1);

	//dst 픽셀값 for문 이용해 채우기
	for (int y = 1; y < src.rows-1; y++) { //최외곽은 엣지가 적용되지 않지만 이걸 감안해서 코드작성. ( 개개별로 조건문 달면 되나 코드가 좀 더러워지기에 이 와 같이 작성함)
		for (int x = 1; x < src.cols-1; x++) {
			//입력영상의 픽셀값 접근 , 소벨마스크
			int v1 = src.at<uchar>(y - 1, x + 1) //각각의 좌표에서 주변의 픽셀값들의 덧셈과 뺄셈을 소벨필터 형태로 연산하여 v1, v2 만듦
				+ src.at<uchar>(y, x + 1) * 2
				+ src.at<uchar>(y + 1, x + 1)
				- src.at<uchar>(y - 1, x - 1)
				- src.at<uchar>(y, x - 1) * 2
				- src.at<uchar>(y + 1, x - 1);
			int v2 = src.at<uchar>(y + 1, x + 1) 
				+ src.at<uchar>(y + 1, x) * 2
				+ src.at<uchar>(y + 1, x - 1)
				- src.at<uchar>(y - 1, x + 1)
				- src.at<uchar>(y - 1, x) * 2
				- src.at<uchar>(y - 1, x - 1);

			//해당 소벨형태 값들을 잘 표현하고자 128이란 grayscale 중간값을 더해서 결과 그레이스케일 픽셀값이 흰색에 가까우면 그레디언트값이 큰 값, 급격히 증가하는 부분.
			//0에 가까운 그레이스케일 값들은 값이 급격히 감소, 128에 가까운 값은 픽셀값 변화가 크지 않은 부분. 
			dx.at<uchar>(y, x) = saturate_cast<uchar>(v1 + 128); // 미분값이 커지는 부분과 작아지는 부분 모두 엣지가 잡혀짐
			dy.at<uchar>(y, x) = saturate_cast<uchar>(v2 + 128);
		}
	}
	imshow("src", src);
	imshow("dx", dx);
	imshow("dy", dy);
	waitKey();
}

이전에 소벨필터 코드를 변형해서 그레디언트 크기까지 출력해보자.

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

using namespace std;
using namespace cv;

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

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

	Mat dx = Mat::zeros(src.rows, src.cols, CV_8UC1); // Gray scale 형식
	Mat dy = Mat::zeros(src.rows, src.cols, CV_8UC1);
	Mat mag = Mat::zeros(src.rows, src.cols, CV_8UC1); //그레디언트 크기 표현.
	 
	//dst 픽셀값 for문 이용해 채우기
	for (int y = 1; y < src.rows-1; y++) { //최외곽은 엣지가 적용되지 않지만 이걸 감안해서 코드작성. ( 개개별로 조건문 달면 되나 코드가 좀 더러워지기에 이 와 같이 작성함)
		for (int x = 1; x < src.cols-1; x++) {
			//입력영상의 픽셀값 접근 , 소벨마스크
			int v1 = src.at<uchar>(y - 1, x + 1) //각각의 좌표에서 주변의 픽셀값들의 덧셈과 뺄셈을 소벨필터 형태로 연산하여 v1, v2 만듦
				+ src.at<uchar>(y, x + 1) * 2
				+ src.at<uchar>(y + 1, x + 1)
				- src.at<uchar>(y - 1, x - 1)
				- src.at<uchar>(y, x - 1) * 2
				- src.at<uchar>(y + 1, x - 1);
			int v2 = src.at<uchar>(y + 1, x + 1) 
				+ src.at<uchar>(y + 1, x) * 2
				+ src.at<uchar>(y + 1, x - 1)
				- src.at<uchar>(y - 1, x + 1)
				- src.at<uchar>(y - 1, x) * 2
				- src.at<uchar>(y - 1, x - 1);

			//해당 소벨형태 값들을 잘 표현하고자 128이란 grayscale 중간값을 더해서 결과 그레이스케일 픽셀값이 흰색에 가까우면 그레디언트값이 큰 값, 급격히 증가하는 부분.
			//0에 가까운 그레이스케일 값들은 값이 급격히 감소, 128에 가까운 값은 픽셀값 변화가 크지 않은 부분. 
			dx.at<uchar>(y, x) = saturate_cast<uchar>(v1 + 128); // 미분값이 커지는 부분과 작아지는 부분 모두 엣지가 잡혀짐
			dy.at<uchar>(y, x) = saturate_cast<uchar>(v2 + 128);
			mag.at<uchar>(y, x) = saturate_cast<uchar>(sqrt(v1 * v1 + v2 * v2));
		}
	}
	resize(src, src, Size(400, 400));
	resize(dx, dx, Size(400, 400));
	resize(dy, dy, Size(400, 400));
	resize(mag, mag, Size(400, 400));
	imshow("src", src);
	imshow("dx", dx);
	imshow("dy", dy);
	imshow("mag", mag);
	waitKey();
}

1) 입력영상 2) x축방향 미분 3)y축방향 미분  4)gradient 크기표현 형상

4)을 통해 좌측 막대부분에 변화가 있다는걸 알 수 있음.

dx, dy 이미지 미분값에 128을 더한 결과.

mag는 128을 더하지 않음

 

엣지 이미지 형성 방법.

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

using namespace std;
using namespace cv;

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

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

	Mat dx = Mat::zeros(src.rows, src.cols, CV_8UC1); // Gray scale 형식
	Mat dy = Mat::zeros(src.rows, src.cols, CV_8UC1);
	Mat mag = Mat::zeros(src.rows, src.cols, CV_8UC1); //그레디언트 크기 표현.
	 
	//dst 픽셀값 for문 이용해 채우기
	for (int y = 1; y < src.rows-1; y++) { //최외곽은 엣지가 적용되지 않지만 이걸 감안해서 코드작성. ( 개개별로 조건문 달면 되나 코드가 좀 더러워지기에 이 와 같이 작성함)
		for (int x = 1; x < src.cols-1; x++) {
			//입력영상의 픽셀값 접근 , 소벨마스크
			int v1 = src.at<uchar>(y - 1, x + 1) //각각의 좌표에서 주변의 픽셀값들의 덧셈과 뺄셈을 소벨필터 형태로 연산하여 v1, v2 만듦
				+ src.at<uchar>(y, x + 1) * 2
				+ src.at<uchar>(y + 1, x + 1)
				- src.at<uchar>(y - 1, x - 1)
				- src.at<uchar>(y, x - 1) * 2
				- src.at<uchar>(y + 1, x - 1);
			int v2 = src.at<uchar>(y + 1, x + 1) 
				+ src.at<uchar>(y + 1, x) * 2
				+ src.at<uchar>(y + 1, x - 1)
				- src.at<uchar>(y - 1, x + 1)
				- src.at<uchar>(y - 1, x) * 2
				- src.at<uchar>(y - 1, x - 1);

			//해당 소벨형태 값들을 잘 표현하고자 128이란 grayscale 중간값을 더해서 결과 그레이스케일 픽셀값이 흰색에 가까우면 그레디언트값이 큰 값, 급격히 증가하는 부분.
			//0에 가까운 그레이스케일 값들은 값이 급격히 감소, 128에 가까운 값은 픽셀값 변화가 크지 않은 부분. 
			dx.at<uchar>(y, x) = saturate_cast<uchar>(v1 + 128); // 미분값이 커지는 부분과 작아지는 부분 모두 엣지가 잡혀짐
			dy.at<uchar>(y, x) = saturate_cast<uchar>(v2 + 128);
			mag.at<uchar>(y, x) = saturate_cast<uchar>(sqrt(v1 * v1 + v2 * v2));
		}
	}

    //threshold 150으로 설정 시
	Mat dst = mag > 150; //150보다 클 시 흰색 그렇치 않으면 검정으로 만들기.  '>' operating 연산자.
	//dst는 그레이스케일 형식을 따르며 0과 255으로 설정됨
	resize(dst, dst, Size(400, 400));
	resize(src, src, Size(400, 400));
	resize(dx, dx, Size(400, 400));
	resize(dy, dy, Size(400, 400));
	resize(mag, mag, Size(400, 400));
	imshow("src", src);
	//imshow("dx", dx);
	//imshow("dy", dy);
	imshow("mag", mag);
	imshow("dst", dst);
	waitKey();
}

 

엣지가 연한부분은 결과영상에서 엣지 표현이 잘되지 않은 걸 볼 수 있음.

위 코드에서 150 은 threshold를 의미.

임계값 조절 통해 엣지 검출하면 되나 임계값영역이 클수록 ( 즉 임계값 기준 낮을 수록) 노이즈와 같은 성분들이 엣지로 나타날 수있기에 이 점들을 고려하여 임계값을 조절해야함.

 

여기까지

소벨필터를 이용한 결과를 x축 미분성분,   y축 방향 미분성분 그리고   magnitude 부분 직접 계산해 구현. 

2. 소벨  필터 이용한 에지 검출

위 직접 소벨필터를 구현한 것을 대체한 함수는 Sobel 함수이다.

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);

src: 입력영상

dst: 출력영상, src와 같은 크기, 같은 채널 수

ddepth:  출력영상 깊이  ( -1:입력영상 동일 형태 , CV_32F: 출력영상을 CV_32F 만들고자 할 경우)   //자료형 지정 flag , FILTER2D 인자와 동일한 형태

dx, dy: x방향과 y방향으로의 미분 차수

ksize: 커널 크기

1 3x1 또는 1x3 커널 사용
CV_SCHARR 3x3 Scharr 커널 사용. Scharr()함수 사용과 같음.
3, 5, 7 3x3, 5x5, 7x7 커널 사용

 

scale: (Optional) 연산 결과에 추가적으로 곱할 값.

delta: (Optional) 연산 결과에 추가적으로 더할 값

borderType: 가장 자리 픽셀 확장 방식

 

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

using namespace std;
using namespace cv;

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

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

	Mat dx, dy, mag;
	Sobel(src, dx, CV_32F, 1, 0); //X축 미분 //3번쨰 인자 CV_32F설정. -1주면 그레이스케일 영상으로 나옴. 미분값이 0으로 설정되어 값손실큼.
	Sobel(src, dy, CV_32F, 0, 1); // y축미분
	magnitude(dx, dy, mag); // 3번쨰 인자 크기결과 이미지 지정
	//dx, dy 행렬타입이 mag 결과영상 타입과 동일한 CV_32F 타입 행렬이 생성
	mag.convertTo(mag, CV_8UC1); //CV_32F 행렬을 화면에 보여주기 어렵기에 CV_8UC1 타입으로 바꿈
	
	Mat dst = mag > 150; //150보다 클 시 흰색 그렇치 않으면 검정으로 만들기.  '>' operating 연산자.
	//dst는 그레이스케일 형식을 따르며 0과 255으로 설정됨
	resize(dst, dst, Size(400, 400));
	resize(src, src, Size(400, 400));
	resize(dx, dx, Size(400, 400));
	resize(dy, dy, Size(400, 400));
	resize(mag, mag, Size(400, 400));
	imshow("src", src);
	//imshow("dx", dx);
	//imshow("dy", dy);
	imshow("mag", mag);
	imshow("dst", dst);
	waitKey();
}

128 더하지 않음.,

mag: 그래디언트 크기 (edge 부분만 흰색으로나옴)

 dst: mag > 150 

 

edge(dst) 이미지의 단점은 노이즈 영향 크며 구해진 엣지 선들을 보면 한 픽셀로 구성된 엣지 이미지가 아닌 여러 픽셀들로 구성된 경우가 많다. gradient크기값이 크다 작다 할 경우 threshold 지정시 어떤 부분은 엣지가 1픽셀, 어느 부분은 5픽셀등으로 표현된다. 

이러한 소벨엣지 문제를 캐니 엣지에서는 해결 할 수있다.

'프로그래머스 > OPENCV' 카테고리의 다른 글

허프변환 알고리즘  (0) 2022.12.08
캐니 에지 검출기  (0) 2022.12.07
에지검출과 소벨필터(1)  (0) 2022.12.07
색상범위 관련  (0) 2022.12.07
특정 색상 영역 추출 하기 [다시]  (0) 2022.12.07

+ Recent posts