케니 알고리즘이란?
널리 사용되는 효과적인 에지 검출 알고리즘이며 허프 변환을 적용하기 전에 이미지를 전처리하여 에지를 찾는 단계에서 핵심입니다.
1. 노이즈 감소 - 가우시안 필터링
- 에지 검출은 픽셀 값의 미세한 변화에 민감하기 때문에, 이미지에 존재하는 노이즈는 거짓 에지를 생성할 수 있습니다. 이를 방지하기 위해 노이즈를 먼저 제거해야 합니다.
2. 그레디언트 강도 변화 계산
- 이미지에서 픽셀 값의 변화율을 측정하여 에지 후보 영역과 방향을 찾습니다.
3. 비 최대 억제
- 에지의 두께를 단일 픽셀로 얇게 만들어 하나의 에지에 대해 여러 개의 에지 응답이 나타나는 것을 방지합니다.
4. 이중 임계값 적용 및 히스테리시스 에지 트래킹
- 강력한 에지와 약한 에지를 구분하고, 연결성을 이용하여 실제 에지를 정확하게 결정합니다.
- 이중 임계값으로 분류된 강한 에지와 약한 에지를 연결하여 최종 에지를 확정합니다.
[Open Cv 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);
|
• image
|
8비트 입력 영상
|
|
• dx
|
입력 영상의 x 방향 미분 영상. CV_16SC1 또는 CV_16SC3
|
|
• dy
|
입력 영상의 y 방향 미분 영상. CV_16SC1 또는 CV_16SC3
|
|
• edges
|
출력 에지 영상. 입력 영상과 크기가 같은 8비트 단일 채널 영상입니다.
|
|
• threshold1
|
히스테리시스 에지 검출을 위한 첫 번째 임계값
|
|
• threshold2
|
히스테리시스 에지 검출을 위한 두 번째 임계값
|
|
• apertureSize
|
그래디언트 계산을 위한 소벨 마스크 크기
|
|
• L2gradient
|
그래디언트 크기 계산 시 L2 노름을 사용하려면 true를 지정합니다. 이 값이 false이면 L1 노름을 사용합니다.
|
※ 두 번째 함수는 이미 x 방향과 y 방향의 미분 영상을 가지고 있을 때 사용합니다.
허프 변환이란?
허프 변환의 핵심은 2차원 영상 공간(x,y)의 점들을 파라미터 공간으로 변환하여 직선 또는 원의 기하학적인 특징을 찾아내는 것입니다.
1. 파라미터 공간으로의 전환
- 직선 방정식의 한계 극복
- y=ax+b는 세로로 곧게 서 있는 수직선(x=상수)을 표현할 수 없다는 단점이 있습니다. (기울기 a가 무한대가 되기 때문)
- 극좌표계 사용
- 직선을 원점으로부터의 수직 거리 ρ (rho)와 x축과 이루는 각도 θ (theta)로 표현
- xcosθ + ysinθ = ρ
※ 극좌표계에서는 xy 공간의 한 점이 ρθ 공간에 하나의 곡선으로 표현되고 반대로 xy 공간의 직선은 ρθ 공간에서 한 점으로 표현됩니다.
2. 에지 검출 : 이미지에서 주로 캐니 알고리즘을 활용하여 경계선 찾기
3. 파라미터 공간으로 변환 및 투표 : 에지로 판별된 픽셀에 대해, 특정 픽셀을 지나가는 모든 직선의 ρθ 값을 계산
4. 직선 검출 원리 : 축적 배열
a. 축적 배열 구축 : 이렇게 계산된 ρθ값들을 축적 배열이라는 2차원 배열에 투표하듯 기록 해당 (ρ,θ) 좌표 위치의 배열 값들 1씩 증가
-
- 양자화 : ρ, θ는 실수 값을 가질 수 있기에 축적 배열에 저장하기 위해 일정 간격으로 나누는 양자화 필요, 간격을 촘촘하게 나누면 정확도는 높아지나 연산 시간 증가 그렇기에 적절한 값 필요
b. 최대값 찾기
-
- 축적 배열에서 가장 높은 값(가장 많은 투표를 받은 곳)을 갖는 (ρ,θ) 쌍을 찾습니다. 이 점들이 바로 이미지 직선을 나타내는 파라미터 입니다.
HoughLines() 함수: 직선의 (ρ,θ) 반환
- 이미지에서 검출된 직선의 (ρ,θ) 파라미터를 반환, 직선 자체의 방정식
HoughLinesP() 함수: 직선의 시작점과 끝점 반환 (확률적 허프 변환)
- 일반 허프 변환보다 더 효율적이며, 직선의 (ρ,θ) 파라미터 대신 직선의 시작 점과 끝 점 좌표 (x_1, y_1, x_2, y_2) 를 직접 반환 따라서 선분을 찾는 데 적합합니다.
실습
케니 엣지 검출 실습
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
void canny_edge()
{
// 이미지를 그레이스케일로 로드
Mat src = imread("lenna.bmp", IMREAD_GRAYSCALE);
// 이미지 로드 성공 여부 확인
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
// 엣지 검출 결과를 저장할 Mat 객체 선언
Mat dst1, dst2;
// Canny 엣지 검출 수행 (첫 번째 설정)
// Canny(입력 이미지, 출력 이미지, 최소 임계값, 최대 임계값)
// 최소 임계값: 50, 최대 임계값: 100
Canny(src, dst1, 50, 100);
// Canny 엣지 검출 수행 (두 번째 설정)
// 최소 임계값: 50, 최대 임계값: 150
// 최대 임계값이 높아지면 엣지로 판단되는 조건이 더 까다로워져서 결과 엣지가 더 적게 나올 수 있다.
Canny(src, dst2, 50, 150);
imshow("src (Original Grayscale)", src);
imshow("dst1 (Thresholds: 50, 100)", dst1);
imshow("dst2 (Thresholds: 50, 150)", dst2);
waitKey();
destroyAllWindows();
}
int main()
{
canny_edge(); // Canny 엣지 검출 함수 호출
return 0;
}

허프 변환 직전 검출 실습
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
void hough_lines()
{
// 이미지를 그레이스케일로 로드
Mat src = imread("building.jpg", IMREAD_GRAYSCALE);
// 이미지 로드 성공 여부 확인
if (src.empty()) {
cerr << "Image load failed! (Check if 'building.jpg' exists)" << endl;
return;
}
// Canny 엣지 검출 수행
Mat edge;
// 최소 임계값: 50, 최대 임계값: 150
Canny(src, edge, 50, 150);
// 표준 허프 변환을 위한 변수 선언
// 검출된 직선의 정보를 저장할 벡터
vector<Vec2f> lines;
// HoughLines 함수 실행
// edge: 입력 엣지 이미지
// lines: 검출된 직선 정보를 저장할 벡터
// 1: 거리(rho) 분해능 (픽셀 단위)
// CV_PI / 180: 각도(theta) 분해능 (라디안 단위, 1도)
// 250: 직선으로 판단하기 위한 임계값 (Threshold) - 누적 평면에서 이 값보다 큰 값만 직선으로 인정
HoughLines(edge, lines, 1, CV_PI / 180, 250);
// 엣지 이미지를 컬러 이미지로 변환하여 직선을 그릴 준비
Mat dst;
// Canny 결과(흑백)에 직선을 그리기 위해 BGR 포맷으로 변환
cvtColor(edge, dst, COLOR_GRAY2BGR);
// 검출된 모든 직선을 결과 이미지(dst)에 그리기
for (size_t i = 0; i < lines.size(); i++) {
// 직선의 파라미터 추출
float r = lines[i][0], t = lines[i][1];
// rho-theta 파라미터를 이용하여 직선의 양 끝점 계산
double cos_t = cos(t), sin_t = sin(t);
double x0 = r * cos_t, y0 = r * sin_t; // 원점에서 직선에 내린 수선의 발 좌표
// 긴 직선을 그리기 위한 임의의 길이 (1000픽셀)
double alpha = 1000;
// 직선의 한 끝점 계산 (x0, y0)에서 theta 방향의 법선 방향으로 alpha만큼 이동
Point pt1(cvRound(x0 + alpha * (-sin_t)), cvRound(y0 + alpha * cos_t));
// 직선의 다른 끝점 계산 (x0, y0)에서 반대 방향으로 alpha만큼 이동
Point pt2(cvRound(x0 - alpha * (-sin_t)), cvRound(y0 - alpha * cos_t));
// 결과 이미지에 빨간색 직선 그리기
line(dst, pt1, pt2, Scalar(0, 0, 255), 2, LINE_AA); // Scalar(B, G, R)
}
imshow("src (Original Grayscale)", src);
imshow("dst (Hough Lines)", dst);
waitKey(0);
destroyAllWindows();
}
int main()
{
hough_lines(); // 허프 변환 함수 호출
return 0;
}
허프 변환 원 검출 실습
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
void hough_circles()
{
// 이미지를 그레이스케일로 로드
Mat src = imread("coins.png", IMREAD_GRAYSCALE);
// 이미지 로드 성공 여부 확인
if (src.empty()) {
cerr << "Image load failed! (Check if 'coins.png' exists)" << endl;
return;
}
// 허프 원 변환 전처리: 노이즈 감소 (블러 처리)
Mat blurred;
// (3x3) 커널 크기로 평균 블러 적용
blur(src, blurred, Size(3, 3));
// 검출된 원의 정보를 저장할 벡터 선언
// Vec3f는 (x, y, r) 세 개의 float 값 (원의 중심 좌표와 반지름)을 저장합니다.
vector<Vec3f> circles;
// 5. HoughCircles 함수 실행
HoughCircles(
blurred, // 입력 이미지 (일반적으로 블러 처리된 8비트 그레이스케일)
circles, // 검출된 원의 정보를 저장할 벡터
HOUGH_GRADIENT, // 검출 방법: 그레이디언트 기반 허프 변환 (OpenCV에서 권장)
1, // dp: 이미지 해상도 역비율. 1이면 입력과 동일
50, // minDist: 검출된 원의 중심 사이 최소 거리 (픽셀 단위).
// 이 값이 작으면 하나의 원이 여러 개로 검출될 수 있음.
150, // param1: Canny 엣지 검출에 사용되는 최대 임계값 (HOUGH_GRADIENT 전용)
30, // param2: 축적 배열의 임계값. 이 값이 작을수록 더 많은 원이 검출될 수 있음.
0, // minRadius: 검출할 원의 최소 반지름 (0은 제한 없음)
0 // maxRadius: 검출할 원의 최대 반지름 (0은 제한 없음)
);
// 결과 이미지를 위한 컬러 이미지 준비 (원본 이미지 사용)
Mat dst;
// 흑백 원본(src)에 원을 그리기 위해 BGR 포맷으로 변환
cvtColor(src, dst, COLOR_GRAY2BGR);
// 검출된 모든 원을 결과 이미지(dst)에 그리기
for (Vec3f c : circles) {
// 원의 중심 좌표와 반지름 추출
Point center(cvRound(c[0]), cvRound(c[1])); // 중심 (x, y)
int radius = cvRound(c[2]); // 반지름 (r)
// 결과 이미지에 빨간색 원 그리기 (두께 2, 안티앨리어싱 적용)
circle(dst, center, radius, Scalar(0, 0, 255), 2, LINE_AA); // Scalar(B, G, R)
}
imshow("src (Original Grayscale)", src);
imshow("dst (Hough Circles)", dst);
waitKey(0);
destroyAllWindows();
}
int main()
{
hough_circles(); // 허프 원 변환 함수 호출
return 0;
}

'학부 생활 + 랩실 > OpenCv' 카테고리의 다른 글
| Open Cv 스터디 - 3 (객체 검출 - qr 코드 검출) (1) | 2026.03.16 |
|---|---|
| Open Cv 스터디 - 1 (1) | 2026.03.16 |