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 |