10장. Sobel 과 Laplacian 경계 검출
실습 2 — cv2.Sobel 로 가로·세로 경계 찾기
cv2.Sobel 로 가로 기울기와 세로 기울기를 구합니다. 결과 자료형을 uint8 로 받았을 때 사라지는 경계를 세어 보고, CV_64F 와 cv2.convertScaleAbs 로 바르게 담는 법을 확인합니다.
1단계. CV_64F 로 가로·세로 기울기 구하기
import cv2
import numpy as np
img = cv2.imread("ch10_shapes.png", cv2.IMREAD_GRAYSCALE)
# Sobel(그림, 결과 자료형, dx, dy, ksize): dx=1 이면 가로 기울기
gx = cv2.Sobel(img, # src: 흑백 그림
cv2.CV_64F, # ddepth: 음수까지 담는 64비트 소수
1, # dx: 가로로 한 번 뺀다
0, # dy: 세로로는 빼지 않는다
ksize=3) # ksize: 3×3 커널
# dx=0, dy=1 이면 세로 기울기
gy = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
print("자료형:", gx.dtype)
print("gx 의 가장 작은 값과 큰 값:", int(gx.min()), int(gx.max()))
print("네모 왼쪽 변 gx(가로 58~61):", [int(v) for v in gx[80, 58:62]])
print("네모 오른쪽 변 gx(가로 138~141):", [int(v) for v in gx[80, 138:142]])
print("네모 위 변 gy(세로 38~41):", [int(v) for v in gy[38:42, 100]])
print("네모 위 변 gx(세로 38~41):", [int(v) for v in gx[38:42, 100]])자료형: float64
gx 의 가장 작은 값과 큰 값: -720 720
네모 왼쪽 변 gx(가로 58~61): [0, 600, 600, 0]
네모 오른쪽 변 gx(가로 138~141): [0, -600, -600, 0]
네모 위 변 gy(세로 38~41): [0, 600, 600, 0]
네모 위 변 gx(세로 38~41): [0, 0, 0, 0]2단계. uint8 로 받으면 사라지는 경계 세기
# 결과 자료형을 CV_8U(uint8)로 받는다. -1 을 주어도 원본과 같은 uint8 이 된다
as_u8 = cv2.Sobel(img, cv2.CV_8U, 1, 0, ksize=3)
print("uint8 로 받은 값(왼쪽 변, 오른쪽 변):", int(as_u8[80, 59]), int(as_u8[80, 140]))
# + 인 칸, - 인 칸, uint8 에서 0 이 아닌 칸을 센다
pos = int((gx > 0).sum())
neg = int((gx < 0).sum())
kept = int((as_u8 > 0).sum())
print(f"+ 인 칸 {pos}, - 인 칸 {neg}, uint8 에 남은 칸 {kept}")
print(f"uint8 에서 사라진 칸의 비율: {neg / (pos + neg):.3f}")uint8 로 받은 값(왼쪽 변, 오른쪽 변): 255 0
+ 인 칸 1410, - 인 칸 834, uint8 에 남은 칸 1410
uint8 에서 사라진 칸의 비율: 0.3723단계. convertScaleAbs 로 바르게 담기
# convertScaleAbs: 배율을 곱하고 절댓값을 취한 뒤 0~255 로 잘라 uint8 로
abs_x = cv2.convertScaleAbs(gx)
abs_y = cv2.convertScaleAbs(gy)
print("절댓값으로 담은 값(왼쪽 변, 오른쪽 변):", int(abs_x[80, 59]), int(abs_x[80, 140]))
print("0 이 아닌 칸:", int((abs_x > 0).sum()))
# 600 이 255 로 잘리지 않게 배율(alpha)을 준다. 가장 큰 절댓값이 255 가 되게 한다
alpha = 255 / np.abs(gx).max()
scaled = cv2.convertScaleAbs(gx, alpha=alpha)
print("배율:", round(float(alpha), 3), "/ 왼쪽 변의 값:", int(scaled[80, 59]))절댓값으로 담은 값(왼쪽 변, 오른쪽 변): 255 255
0 이 아닌 칸: 2244
배율: 0.354 / 왼쪽 변의 값: 2124단계. 네 그림을 견주어 보기
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False
fig, axes = plt.subplots(2, 2, figsize=(8, 6))
panels = [("원본", img), ("uint8 로 받은 gx", as_u8),
("|gx| (CV_64F → convertScaleAbs)", abs_x), ("|gy| (CV_64F → convertScaleAbs)", abs_y)]
# axes.flat: 2×2 칸을 한 줄로 펴서 차례로 꺼낸다
for ax, (title, arr) in zip(axes.flat, panels):
ax.imshow(arr, cmap="gray", vmin=0, vmax=255)
ax.set_title(title)
ax.axis("off")
plt.tight_layout()
plt.show()
코드 설명
cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)은 네모의 왼쪽 변(가로 59·60)에서 600, 오른쪽 변(139·140)에서 −600 을 돌려줍니다. 위 변(세로 39·40)에서는 gy 가 600 이고 gx 는 0 이라, 가로로 누운 변은 가로 기울기에 나오지 않습니다. 그림 전체에서 gx 는 −720 에서 720 사이입니다.- 결과를
cv2.CV_8U로 받으면 왼쪽 변은 255(600 이 넘쳐 잘림), 오른쪽 변은 0(음수라 잘림)이 됩니다. + 인 칸 1,410개는 남고 − 인 칸 834개가 모두 사라져, 경계 칸의 0.372 를 잃습니다. cv2.convertScaleAbs(gx)는 절댓값을 취하므로 양쪽 변이 모두 255 가 되고, 0 이 아닌 칸이 1,410 + 834 = 2,244개로 모두 남습니다.alpha=255/720(약 0.354)을 주면 가장 큰 값이 255 에 맞춰져, 600 은 212 로 잘리지 않고 담깁니다.- 그림에서
uint8로 받은 gx 는 네모의 오른쪽 변, 원의 오른쪽 반, 세모의 오른쪽 변이 없습니다. 계단 띠는 왼쪽에서 오른쪽으로 밝아지므로 안쪽 선은 남고, 띠가 끝나 바탕으로 어두워지는 맨 오른쪽 선만 사라집니다.