Edge Detection
Introduction
- Edge pixel - 이미지의 Intensity가 급격하게 변화하는 지점에 있는 픽셀
- Edge - Edge pixel의 집합

How to detect edges?
1D
1차 미분 (first derivative) 의 크기를 사용하여 edge를 감지

2D
Image gradient를 사용하여 edge를 검출
이미지를 intensity 값을 가진 2차원 함수 라고 했을 때,
Gradient는 가 각 지점에서 어느 방향으로 빠르게 변하는지를 나타내는 벡터이다.
밝기 변화가 가장 심한 방향과 그 변화량을 의미한다.
- = : x축 방향으로의 밝기 변화율 (옆 픽셀 간 밝기 차이)
- = : y축 방향으로의 밝기 변화율 (위아래 픽셀 간 밝기 차이)
이렇게 구한 Gradient vector 의 크기 (Magnitude) 는 밝기 변화가 얼마나 강한지를 나타낸다.
Gradient Magnitude는 피타고라스 정리를 이용해 계산한다.
Gradient Direction은 밝기가 가장 급격하게 증가하는 방향 으로, 아크탄젠트 함수로 계산한다.
Gradient Vector의 방향은 Edge Direction과 수직인 관계이다.

Edge Detection 시 노이즈의 영향
이미지에 노이즈가 있으면 edge detection 시 잘못된 결과가 나올 수 있으므로 median filtering이나 average filtering으로 이미지 smoothing하는 과정이 필요

Sobel operator
이미지의 각 픽셇에서 밝기 변화의 정도를 x축, y축의 Gradient를 근사치로 계산한다.
바로 옆 픽셀이나 아래의 픽셀과의 변화를 이용해서 기울기를 구하고,
단순 차분 연산에 가중치를 줘 수평,수직 방향 edge를 검출한다.

두 방향의 기울기의 magnitude를 각 픽셀에 대해 피타고라스 정리로 풀기에는 연산 오버헤드가 크기 때문에 두 절댓값을 더하는 식으로 근사치를 구한다
아래는 각각 gradient 연산자만 적용, 5x5 avg 필터링 이후에 적용한 결과이다.

Canny Edge Detector
알고리즘
- Gaussian filter를 이용해 이미지의 노이즈를 제거
- Gradient Magnitude와 Angle을 계산
- Sobel Edge mask를 사용한다.
- nonmaxima suppression 적용
- 에 가장 가까운 방향 를 찾는다.
- 가 적어도 이웃한 픽셀 중 적어도 하나보다 작다면, 해당 픽셀 값을 0으로 설정
- double tresholding, connectivity analysis
- : edge
- : non-edge
- Otherwise : undertermined → connectivity analysis
Line Detection
Hough transform 개념
직선은 로 정의할 수 있다.
Hough transform은 이 식을 매개변수 , 에 관한 식으로 변환한다
이러면 가 고정된 값일 때 이 픽셀을 통과하는 모든 가능한 직선의 매개변수 조합을 찾을 수 있다.

근데 카르테시안 좌표계를 사용하면 수직선의 경우 기울기 a 가 무한대 가 되는 문제가 있음
실제 Hough transform은 대신 극 좌표게 () 표현을 사용한다.

알고리즘
- edge 정보만 포함 된 binary edge 이미지를 얻는다.
- 공간 분할
- accumulator (누적기) 셀의 개수를 조사해서 픽셀이 많이 모여있는 곳을 찾는다.

Circle detection
Hough Transform은 같이 매개변수로 정의될 수 있는 모든 도형에 적용 가능하다.
원의 방정식은 로 매개변수가 3개이므로 3차원 공간이 된다.
Code
Sobel Edge Detector
void testSobelEdgeDetector() {
Mat image;
Mat blur, grad_x, grad_y, abs_grad_x, abs_grad_y, result;
image = imread("lena.png", 0);
GaussianBlur(image, blur, Size(5, 5), 5, 5, BORDER_DEFAULT);
Sobel(blur, grad_x, CV_16S, 1, 0, 3);
convertScaleAbs(grad_x, abs_grad_x);
Sobel(blur, grad_y, CV_16S, 0, 1, 3);
convertScaleAbs(grad_y, abs_grad_y);
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, result);
imshow("X", abs_grad_x);
imshow("Y", abs_grad_y);
imshow("Input image", image);
imshow("Sobel Edge Detector", result);
waitKey(0);
}GaussianBlur
void GaussianBlur(
InputArray src,
OutputArray dst,
Size ksize,
double sigmaX,
double sigmaY = 0,
int borderType = BORDER_DEFAULT
);ksize: 가우시안 커널의 크기Size(width, height)으로 지정sigmaX: X축 방향 가우시안 표준편차sigmaY: Y축 방향 가우시안 표준편차
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
);ddepth: 출력이미지의 데이터 타입- 미분 결과가 음수 값을 가질 수 있기 때문에
CV_8U는 정보가 손실됨 →CV_16S사용
- 미분 결과가 음수 값을 가질 수 있기 때문에
dx: X축 방향 미분 차수dy: Y축 방향 미분 차수ksize: 소벨 커널 크기 → 1, 3, 5, 7 중 하나
Canny Edge Operator
void testCannyEdgeOperator() {
Mat image, canny;
image = imread( "lena.png", 0);
Canny(image, canny, 190, 200, 3);
imshow("input", image);
imshow("canny", canny);
waitKey(0);
}Canny
void Canny(
InputArray image,
OutputArray edges,
double threshold1,
double threshold2,
int apertureSize = 3,
bool L2gradient = false
);treshold1: 하위 임계값- 이 값보다 gradient 크기가 낮으면 엣지 아닌 것으로 간주
treshold2: 상위 임계값- 이 값보다 크면 무조건 엣지로 채택 (강한 엣지)
thresold1과2사이의 값은 강한 엣지와 연결돼있을 때만 엣지로 채택
opertureSize: 내부적으로 gradient 계산 시 사용하는 소벨 커널 크기L2gradient: gradient 크기 계산하는 방식 선택하는 플래그false:true:
HoughLines
void testHoughLines() {
Mat image, edge, result;
float rho, theta, a, b, x0, y0;
Point p1, p2;
vector<Vec2f> lines;
image = imread("chess_pattern.png");
result = image.clone();
cvtColor(image, image, COLOR_BGR2GRAY);
Canny(image, edge, 50, 200, 3);
HoughLines(edge, lines, 1, CV_PI / 180, 150, 0, CV_PI);
for (int i = 0; i < lines.size(); i++) {
rho = lines[i][0];
theta = lines[i][1];
a = cos(theta);
b = sin(theta);
x0 = a * rho;
y0 = b * rho;
p1 = Point(cvRound(x0 + 1000 * (-b)), cvRound(y0 + 1000 * a));
p2 = Point(cvRound(x0 - 1000 * (-b)), cvRound(y0 - 1000 * a));
line(result, p1, p2, Scalar(0, 0, 255), 3, 8);
}
imshow("input image", image);
imshow("edge", edge);
imshow("Hough Transform", result);
waitKey(0);
}HoughLines
void HoughLines(
InputArray image,
OutputArray lines,
double rho,
double theta,
int threshold,
double srn = 0,
double stn = 0,
double min_theta = 0,
double max_theta = CV_PI
);lines: 검출된 직선 정보를 저장할 벡터vector<Vec2f>rho: (거리) 값의 해상도theta: (각도) 값의 해상도 (라디안)CV_PI / 180⇒ 각도를 1도의 정밀도로 측정threshold: 임계값- 허프 변환 투표 과정에서 직선으로 인정받기 위해 받아야 할 최소 득표 수
srn,srt: 멀티 스케일 허프 변환에 사용되는 파라미터. 기본 허프변환은0min_theta,max_theta: 검출할 직선의 최소 및 최대 각도 범위0~CV_PI
HoughLinesP
void testHoughLinesP() {
Mat image, edge, result;
vector<Vec4i> lines;
image = imread("chess_pattern.png");
result = image.clone();
cvtColor(image, image, COLOR_BGR2GRAY);
Canny(image, edge, 50, 200, 3);
HoughLinesP(edge, lines, 1, CV_PI / 180, 50, 10, 300);
for (int i = 0; i < lines.size(); i++) {
Vec4i l = lines[i];
line(result, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0, 0, 255), 3, 8);
}
imshow("input image", image);
imshow("edge", edge);
imshow("Hough Transform", result);
waitKey(0);
}HoughLinesP
void HoughLinesP(
InputArray image,
OutputArray lines,
double rho,
double theta,
int threshold,
double minLineLength = 0,
double maxLineGap = 0
);lines: 검출된 선분 정보를 저장할 벡터vector<Vec4i>→ 선분의 시작점과 끝점 좌표minLineLength: 검출할 선분의 최소 길이maxLineGap: 하나의 직선으로 간주할 점들 사이의 최대 허용 간격
HoughLines vs. HoughLinesP
Result
HoughLines()는vector<Vec2f>,(rho, theta)를 반환- 무한한 직선의 정보
HoughLinesP()는vector<Vec4i>,(x1,y1), (x2,y2)를 반환- 시작점과 끝점의 좌표
Default parameters
HoughLines(): 전체적인 직선의 속성과 관련된 파라미터 (ex: 탐색할 직선의 최소/최대 각도)HoughLinesP(): 검출된 선분 자체의 속성과 관련된 파라미터 (ex: 최소 선분의 길이나 최대 허용 간격)