10장. Sobel 과 Laplacian 경계 검출
기울기의 크기와 방향
지하철역에서 동쪽으로 3블록, 북쪽으로 4블록 떨어진 식당이 있다고 해 봅시다. 블록을 따라 걸으면 7블록이지만, 하늘을 나는 새는 비스듬히 곧장 5블록만 날아갑니다. 가로 기울기와 세로 기울기도 이렇게 합칩니다. 가로 변화 3 과 세로 변화 4 는 "가장 가파른 쪽으로 5 만큼 변한다"는 뜻입니다.
초록 화살표가 가로 기울기(gx = 3), 보라 화살표가 세로 기울기(gy = 4), 다홍 화살표가 둘을 합친 크기(5)입니다. 다홍 화살표가 가리키는 쪽이 기울기의 방향, 곧 밝기가 가장 가파르게 밝아지는 쪽입니다. 여기서 동쪽 3블록이 gx, 북쪽 4블록이 gy, 새가 날아간 거리가 크기입니다.
크기를 손으로 계산하기
피타고라스 정리는 "빗변의 제곱 = 두 변의 제곱의 합"입니다. 그래서 크기 = √(gx² + gy²) 입니다.
| 칸 | gx | gy | gx² + gy² | 크기 √ |
|---|---|---|---|---|
| 손계산 예 | 3 | 4 | 9 + 16 = 25 | 5 |
| 네모의 왼쪽 변 (세로 80, 가로 59) | 600 | 0 | 360,000 + 0 = 360,000 | 600 |
| 네모의 왼쪽 위 모서리 (세로 40, 가로 60) | 450 | 450 | 202,500 + 202,500 = 405,000 | 약 636.4 |
곧은 변에서는 한쪽 기울기가 0 이라 크기가 다른 쪽 기울기의 절댓값과 같습니다. 모서리처럼 두 방향이 함께 바뀌는 곳에서는 두 값이 합쳐져 더 커집니다.
두 가지 합치는 법
왼쪽 cv2.magnitude 는 피타고라스 정리를 그대로 계산합니다. 오른쪽 cv2.addWeighted 는 두 기울기의 절댓값을 절반씩 더하는 어림값으로, 인터넷 예제에 자주 나옵니다. 어림값은 경계가 가로나 세로로 곧을 때는 실제 크기의 절반, 비스듬할 때는 그보다 크게 나와서, 같은 높이의 경계라도 방향에 따라 밝기가 달라집니다.
| 견줄 것 | cv2.magnitude(gx, gy) | cv2.addWeighted(|gx|, 0.5, |gy|, 0.5, 0) |
|---|---|---|
| 계산 | √(gx² + gy²) | 0.5 × |gx| + 0.5 × |gy| |
| gx 3, gy 4 일 때 | 5 | 3.5 |
| 곧은 변 (600, 0) | 600 | 300 |
| 같은 높이의 경계 | 방향과 관계없이 같은 크기 | 비스듬할수록 상대적으로 크다 |
| 쓸 곳 | 크기로 임계값을 정할 때 | 빠르게 대충 볼 때 |
방향
방향은 "어느 쪽으로 가장 가파르게 밝아지나"입니다. cv2.phase(gx, gy, angleInDegrees=True) 가 칸마다 각도를 0~360 도로 돌려줍니다. 그림의 세로축은 아래로 갈수록 숫자가 커지므로, 0 도는 오른쪽, 90 도는 아래쪽, 180 도는 왼쪽, 270 도는 위쪽입니다.
| 경계 | gx | gy | 방향 (도) | 뜻 |
|---|---|---|---|---|
| 네모의 왼쪽 변 | + | 0 | 0 | 오른쪽으로 갈수록 밝아진다 |
| 네모의 위 변 | 0 | + | 90 | 아래로 갈수록 밝아진다 |
| 네모의 오른쪽 변 | − | 0 | 180 | 왼쪽으로 갈수록 밝아진다 |
짧은 예시
import cv2
import numpy as np
img = cv2.imread("ch10_shapes.png", cv2.IMREAD_GRAYSCALE)
gx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
gy = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
# magnitude: 칸마다 √(gx² + gy²). 두 그림은 크기와 자료형이 같아야 한다
mag = cv2.magnitude(gx, gy)
print(round(float(mag[40, 60]), 1)) # 결과: 636.4 → 모서리
print(round(float(np.sqrt(450 ** 2 + 450 ** 2)), 1)) # 결과: 636.4 → 손계산과 같다
# phase: 칸마다 방향(각도). angleInDegrees=True 면 0~360 도
ang = cv2.phase(gx, gy, angleInDegrees=True)
print([round(float(ang[80, x])) for x in (59, 140)]) # 결과: [0, 180] → 왼쪽 변, 오른쪽 변주의할 점
- 방향은 경계 선 자체가 아니라 경계를 가로지르는 쪽입니다. 세로로 선 변의 방향은 0 도나 180 도(가로)입니다.
- 기울기가 0 인 고른 면에서도
cv2.phase는 각도를 돌려주지만 뜻이 없습니다. 방향은 크기가 큰 칸에서만 읽습니다.