본문 바로가기
학부 생활 + 랩실/OpenCv

Open Cv 스터디 - 2 (케니 알고리즘, 허프 변환)

by 프롭 2026. 3. 16.

 

케니 알고리즘이란?

널리 사용되는 효과적인 에지 검출 알고리즘이며 허프 변환을 적용하기 전에 이미지를 전처리하여 에지를 찾는 단계에서 핵심입니다.

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