#09-1. 에지 검출

Edge

  • 한쪽 방향으로 픽셀 값이 급격하게 바뀌는 부분
    • 어두운 영역 → 갑자기 밝아짐
    • 밝은 영역 → 갑자기 어두워짐
  • 주로 객체와 배경의 경계, 객체와 다른 객체의 경계에서 발생 → 객체의 윤곽 알아내는 유용한 방법
  • 객체 판별을 위한 전처리로 사용

미분과 그래디언트

  • Edge : 픽셀 값의 변화율 측정

    → 변화율이 큰 픽셀 선택

  • 미분(derivative) : 데이터의 변화율, 순간 변화율

    • x의 변화량이 무한히 0에 가까워질 때의 함수값 변화량
    • 함수값 증가 : 양수
    • 함수값 감소 : 음수
  • 급격하게 바뀌는 부분

    • 미분 값이 0보다 훨씬 크거나 훨씬 작은 위치 : a, b
    • c : 일정한 기울기로 증가 → 미분값 : 일정한 양수값
  • 2차원 평면

    • 정형화 X → 미분 공식 적용 X
    • 정수 단위 좌표에 픽셀이 나열되어 있는 이산함수
  • 미분 근사화 방법

    • 1차원 이산함수(영상) 에서 미분 구하는 방법

이산함수에 대한 미분 근사

  • I(x) : 1차원 이산함수 h : 이산 값의 간격 (픽셀의 간격 = 1)
  • 전진 차분
    • I(x + 1) - I(x)
    • 바로 앞에 있는 픽셀에서 자신의 픽셀 값을 뺀 값
  • 후진 차분
    • I(x) - I(x - 1)
    • 자신의 픽셀에서 바로 뒤에 있는 픽셀 값을 뺀 값
  • 중앙 차분
    • (I(x + 1) - I(x - 1)) / 2

    • 바로 앞 뒤에 있는 픽셀값 이용

      → 근사화 오류 가장 적음, 널리 사용

2차원 평면에서의 미분

  • 가로 방향, 세로 방향으로 각각 미분
  • I(x, y) 가로 방향 미분
    • y좌표 고정

    • x축 방향으로만 미분 근사 계산

      → x축 방향으로의 편미분(partial derivative) = Ix

    • 1 x 3 필터 마스크

  • I(x, y) 세로 방향 미분 → x좌표 고정한 상태에서 y축 방향으로의 편미분 = Iy
    • 3 x 1 필터 마스크

에지 검출

  • 미분 결과 시각적 분석 = 미분값 + 128

  • 0 ~ 255 정수 형 변환 → 그레이스케일 영상으로

    원본, x좌표 증가함에 따른 변화, y좌표 증가함에 따른 변화

    • 회색 영역 (128) : 픽셀값 변화 작은 부분
    • 흰색 영역 : 픽셀값 크게 증가하는 부분

그래디언트

  • 2차원 : x축 방향, y축 방향 편미분 모두 사용

    → gradient : x축, y축 방향 미분을 한 번에 벡터로 표현한 것(합성, 방향성)

  • 벡터

    • 크기 (magnitude), 방향(phase) 성분 표현 가능
    • 방향 : 변화 정도가 가장 큰 방향
    • 크기 : 변화율 세기를 나타내는 척도 f\|\nabla f\|
    • 방향
      θ=tan1(fyfx)\theta = \tan^{-1}\left(\frac{f_y}{f_x}\right)
  • Example>

    • 빨간색 화살표 길이 : 그래디언트 크기
    • 화살표 방향 : 그래디언트 벡터의 방향
    • 밝기 차이 클수록, 길게 : c > a, b
    • 노란색 화살표 : 에지의 방향 (그래디언트 수직) → 그래디언트에서 반시계로 90도 꺾은 방향 → 경계선 검출에 활용
  • threshold

    • 에지 여부 판단의 기준값, 임계값, 문턱치
    • 높은 임계값 : 밝기 차이 급격한 에지 픽셀만 검출

마스크 기반 에지 검출

  • x축 방향 편미분 소벨 마스크
    • 현재 행에 대한 중앙 차분 연산 2회

      → 더 큰 가중치 목적

  • y축 방향 편미분 소벨 마스크
  • 이웃행과의 픽셀 값 변화 유사 → 잡음 감소
mx = np.array([[-1, 0, 1],
                [-2, 0, 2],
                [-1, 0, 1]], dtype = np.float32)
my = np.array([[-1, -2, -1],
                [0, 0, 0],
                [1, 2, 1]], dtype = np.float32)
dx = cv2.filter2D(src, -1, mx, delta = 128)
dy = cv2.filter2D(src, - 1, my, delta = 128)

소벨(Sobel) 필터

  • Sobel(src, dst, ddepth, dx, dy, ksize = 3, scale = 1, delta = 0, borderType = BORDER_DEFAULT);
    • ddepth : 출력 영상 깊이 → ddepth = -1 : src와 같은 타입을 사용하는 dst 영상 생성
    • ksize : 소벨 커널의 크기 → ksize = 1 : 3x1, 1x3 커널 / ksize = 3 : 3x3 커널
    • scale : 추가적으로 곱할 값
    • delta : 추가적으로 더할 값
    • borderType : 가장자리 픽셀 확장
  • 고차미분 가능, 대부분 1차 미분 구하는 용도로 사용
    • x(y)방향으로 편미분한 결과 dx(dy) 행렬에 저장

      Mat dx, dy;
      Sobel(src, dx, CV_32FC1, 1, 0);
      Sobel(src, dy, CV_32FC1, 0, 1);
def sobel_edge():
		# src, ksize, scale, delta
    dx = cv2.Sobel(src, cv2.CV_32F, 1, 0)
    dy = cv2.Sobel(src, cv2.CV_32F, 0, 1)

    fmag = cv2.magnitude(dx, dy)   # 실수형 행렬
    mag = np.uint8(np.clip(fmag, 0, 255))   # 그레이스케일로 변환
    # clip : 255 넘어가면 잘라내기
    _, edge = cv2.threshold(mag, 150, 255, cv2.THRESH_BINARY)

    cv2.imshow('src', src)
    cv2.imshow('mag', mag)
    cv2.imshow('edge', edge)

샤르(Scharr) 필터

  • 3x3 소벨 마스크보다 정확한 미분 계산
    • Sobel().ksize = -1 || FILTER_SCHARR : 샤르 마스크 사용

  • Scharr(src, dst, ddepth, dx, dy, scale = 1, delta = 0, borderType = BORDER_DEFAULT);

그래디언트

  • 2차원 벡터의 x 방향 좌표, y방향 좌표 → 그래디언트(벡터) 크기 계산
    • magnitude(x, y, magnitude);
    • x, y : CV_32F, CV_64F 깊이의 행렬(벡터)
    • magnitude(I)=x(I)2+y(I)2\text{magnitude}(I) = \sqrt{x(I)^2 + y(I)^2}
  • 그래디언트 방향 계산
    • phase(x, y, angle, angleInDegrees = false);
    • 출력 angle(I)=atan2(y(I)x(I))\text{angle}(I) = \text{atan2}\left(\frac{y(I)}{x(I)}\right)
    • angleInDegrees : true (단위 degree) / false (단위 radian)

캐니 에지 검출기

  • Sobel 에지 검출 방법의 단점 해결
  • 에지 검출 조건 : 정확한 검출, 정확한 위치, 단일 에지
  • 그래디언트 크기, 방향 모두 고려 → 더 정확한 에지 위치
  • 에지는 서로 연결 ! → 그래디언트가 약하게 나타나는 에지도 검출

캐니 에지 검출기 연산과정

  1. 가우시안 필터링
    • 영상의 잡음 제거 (생략 가능)
    • 에지 세기 감소 : 적절한 표준 편차 선택
  2. 그래디언트 계산
    • 3 x 3 소벨 마스크 사용
    • 가로, 세로 방향 소벨 마스크 필터링 → 그래디언트 크기, 방향 계산
    • 그래디언트 크기 : L2 norm, L1 norm (연산 속도 향상, 기본값)
  3. 비최대 억제 non-maximum suppression
    • 에지가 두껍게 (여러 픽셀) 표현되는 현상 방지
    • 그래디언트 크키 : local maximum인 픽셀만
    • 그래디언트 벡터의 방향과 같은 방향에 있는 인접 픽셀끼리만 검사
  4. 이중 임계값을 이용한 히스테리시스 에지 트래킹
    • 하나의 임계값 : 환경 변화에 민감 → 캐니 에지 검출기 : 두 개의 임계값 사용 THighT_{\text{High}}, TLowT_{\text{Low}}
    • 그래디언트 크기
      • THighT_{\text{High}} : 보다 높으면 무조건 edge → strong edge
      • TLowT_{\text{Low}} ~ THighT_{\text{High}} : edge일 수도 아닐 수도 → weak edge
      • TLowT_{\text{Low}} : 보다 낮으면 무조건 edge 아님
    • hysteresis edge tracking
      • weak edge를 최종적으로 판별

      • strong edge와 연결되어있는지가 기준 : edge direction

OpenCV Canny()

  • Canny(dx,dy, edges, threshold1, threshold2, L2gradient = false);
    • dx(dy) : x(y)방향 미분 영상, CV_16SC1, CV_16SC3
    • edges : 출력 에지 영상
  • Canny(image, edges, thresdhold1, thresdhold2, apertureSize = 3, L2gradient = false)
    • apertureSize : 그래디언트 계산을 위한 소벨 마스크 크기
def canny_edge():
    dst1 = cv2.Canny(src, 50, 100)   # 두 개의 임계값 사용
    dst2 = cv2.Canny(src, 50, 150)

    cv2.imshow('src', src)
    cv2.imshow('dst1', dst1)
    cv2.imshow('dst2', dst2)

→ 임계값 낮출수록 : 더 많은 에지 픽셀 검출, 잡음에 해당하는 픽셀도 검출

#09-2. 직선 검출과 원 검출

허프 변환 직선 검출

  • hough transform

  • edge 검출 → 일직선상에 배열되어있는지 확인

  • 2차원 xy좌표 직선의 방정식 → 파라미터 공간으로 변환

    • y = ax + b
    • a : 기울기 (slope), b : y절편 (y intersection)
    • parameter 공간(a, b) 으로 변환 : b = -xa + y
  • 직선이 많이 교차되는 점(교점) 찾기

    • 축적 배열 (accumulation array) 사용

    • 0으로 초기화된 2차원 배열에서 직선이 지나가는 위치의 배열 원소값++

  • 한계 : x = 3 (기울기 : 무한대) → 표현 불가

    • 극 좌표계 형식의 직선의 방정식 사용 → 국지점 최댓값 발생하는 위치에서의 ρ, θ 값 찾기
    • 로우 : 원점에서 직선까지의 수직 거리
    • ρ θ 공간에서의 교점 : xy 공간의 파란색 직선을 나타내는 파라미터
    • ρ, θ 실수값 → 양자화(quantization) 과정 필요
      • 0 ≤ θ ≤ π\pi → 180단계, 360단계
      • 구간 촘촘할수록 정밀한 직선 검출, 연산 시간 증가

OpenCV HoughLines()

  • HoughLines(image, lines, rho, theta, threshold, srn=0, stn=0, min_theta=0, max_theta=CV_PI)
    • rho : 축적 배열에서 ρ 값의 해상도 (픽셀 단위) → 몇 픽셀 단위로 양자화할 것인지
    • lines : 직선 정보(ρ, θ) 저장
    • theta : 축적 배열에서 θ 값의 해상도 (라디안 단위 주의!)
    • threshold : 축적 배열에서 직선으로 판단할 임계값
  • 영상 위에 빨간색 직선 그려서 나타내기
    # math.pi = 180도, math.pi/180 -> 1도 단위
    edge = cv2.Canny(src, 50, 150)
    lines = cv2.HoughLines(edge, 1, math.pi/180, 250)
    dst = cv2.cvtColor(edge, cv2.COLOR_GRAY2BGR)
    
    if lines is not None:
        for i in range(lines.shape[0]):
            rho = lines[i][0][0]
            theta = lines[i][0][1]
            cos_t = math.cos(theta)
            sin_t = math.sin(theta)
            x0, y0 = rho * cos_t, rho * sin_t
            alpha = 1000   # 충분히 크게 설정 필요
            pt1 = (int(x0 - alpha * sin_t), int(y0 + alpha * cos_t))
            pt2 = (int(x0 + alpha * sin_t), int(y0 - alpha * cos_t))
            cv2.line(dst, pt1, pt2, (0, 0, 255), 2, cv2.LINE_AA)

확률적 허프 변환

  • probabilistic Houghtransform에 의한 직선 검출
  • 직선의 방정식 파라미터 ρ, θ 반환 X → 직선의 시작점, 끝점 반환 (선분 찾기)
  • HoughLinesP(image, lines, rho, theta, threshold, minLineLength = 0, maxLineGap = 0)
    • minLineLength : 검출할 선분의 최소 길이

    • maxLineGap : 직선으로 간주할 최대 에지 점 간격

      edge = cv2.Canny(src,  50, 150)
      lines = cv2.HoughLinesP(edge, 1, math.pi / 180, minLineLength=50, maxLineGap=5)
      dst = cv2.cvtColor(edge, cv2.COLOR_GRAY2BGR)
      
      if lines is not None:
          for i in range(lines.shape[0]):
              pt1 = (lines[i][0][0], lines[i][0][1])
              pt2 = (lines[i][0][2], lines[i][0][3])
              cv2.line(dst, pt1, pt2, (0,0,255), 2, cv2.LINE_AA)

허프 변환 원 검출

  • (xa)2+(yb)2=r2(x-a)^2 + (y-b)^2 = r^2 → 파라미터 3개
  • 3차원 파라미터 공간에서 축적 배열 정의
  • 많은 메모리, 연산 시간 → Hough gradient method 사용

허프 그래디언트

  • 방법

    1. 영상에 존재하는 모든 원의 중심 좌표 찾기

      • 축적 배열 사용 (파라미터 공간X, xy좌표 공간)
      • 입력 영상의 모든 에지 픽셀의 그래디언트 구하기
      • 그래디언트 방향을 따르는 직선상의 축적 배열 값 +1

    2. 원의 중심으로부터 적합한 반지름 구하기

      • 다양한 반지름에 대해 원주 상에 충분히 많은 에지 픽셀이 존재하는지 확인
  • HoughCircles(image, circles, method, dp, minDist, param1 = 100, param2 = 100, minRadius = 0, maxRadius = 0)

    • method : HOUGH_GRADIENT만 지정 가능
    • dp : 입력 영상, 축적 배열의 크기 비율
    • minDist : 인접한 원 중심의 최소 거리
    • param1 : Canny 에지 검출기의 높은 임계값
    • param2 : 축적 배열에서 원 검출을 위한 임계값
    • minRadius, maxRadius : 검출할 원의 최소/최대 반지름
blurred = cv2.blur(src, (3, 3))   # 잡음제거용
circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT, 1, 50,
                            param1=150, param2=30)
dst = cv2.cvtColor(src, cv2.COLOR_GRAY2BGR)
if circles is not None:
    for i in range(circles.shape[1]):
        cx, cy, radius = circles[0][i]
        cv2.circle(dst, (cx, cy), radius, (0,0,255), 2, cv2.LINE_AA)
profile
숭실대학교 컴퓨터학부 21

0개의 댓글