[OpenCV] Perspective Transformation (์๊ทผ ๋ณํ) | ์๊ณก๋ ์์์ ํด์ฃผ๋ ๋ฐฉ๋ฒ
Geometric Transformation
์์์ ๊ธฐํํ์ ๋ณํ์ ํตํด ๋ค์ํ ํํ๋ก ๋ณํ๋ ์ ์๋๋ฐ ์์ ๋์ ๋ฐ๋ผ translation, eclidean,similarity, affine, perspective(projective) ๋ณํ์ผ๋ก ๋๋๋ค. ์ด ์ค์์ perspective transformation์ ์์ ๋๊ฐ ๊ฐ์ฅ ํฌ๋ค. ๋ค์ ๋งํด ๊ฐ์ฅ ๋ง์ ๋ณํ์ ์ค ์ ์๋ ๋ณํ์ด๋ผ๋ ๋ป์ด๋ค. ๋ค์ํ ์ปดํจํฐ ๋น์ ํ๋ก์ ํธ์์ ์นด๋ฉ๋ผ์ ๊ฐ๋์ ๋ฐ๋ผ ์๊ณก๋๋ ๊ฐ์ฒด๋ ํ ์คํธ ๋ค์ ์ ๋ฉด์ผ๋ก ๋ฐ๋ผ๋ณด๋ view๋ก ๋ณํํ๊ธฐ ์ํด affine ๋๋ perspective transformation์ด ์ฌ์ฉ๋๋ค.
Perspective Transformation
๊ทธ ์ค์์ ๊ฐ์ฅ ํฐ ์์ ๋๋ฅผ ๊ฐ์ง๋ perspective transformation์ ์ฌ์ฉ๋ฒ์ ๋ํด ์์๋ณด๋ ค ํ๋ค. ์ฐ๋ฆฌ๊ฐ ๊ฐ์ฅ ํํ๊ฒ ์ ํ ์ ์๋ perspective transformation์ ์์๋ ๋ฌธ์๋ฅผ ์ดฌ์ํ๋ฉด ๋ฌธ์์ ๋ชจ์๋ฆฌ๋ฅผ ์ฐพ์์ ์ ๋ฉด์ผ๋ก ๋ณด๋ view๋ก ๋ณํํด ์ฃผ๋ ๊ธฐ์ ์ด๋ค. ์ค๋งํธํฐ ์ค์บ ์ดํ ๋ฑ์์ ํํ ๋ณผ ์ ์๋ค.
OpenCV์ Perspective Transformation์ ์ด๋ฏธ์ง์์ ์ ํํ ๊ด์ฌ ์์ญ์ ์๊ทผ ๋ณํ(perspective transform)ํ์ฌ ๋ค๋ฅธ ๊ด์ ์์ ๋ณด๋ ๊ฒ์ฒ๋ผ ๋ณ๊ฒฝํ๋ ๊ธฐ์ ๋ก ์นด๋ฉ๋ผ์ ์๊ณก ๋ณด์ , ์ด๋ฏธ์ง๋ฅผ ๋ณด์ ํ๊ฑฐ๋ ์ธก์ ํ ๋ ์ฌ์ฉ๋๋ค.
์ฌ์ฉ ๋ฐฉ๋ฒ์ ์ด๋ฏธ์ง์์ ๊ด์ฌ ์์ญ์ ์ ํํ๊ณ ๊ทธ ์์ญ์ ๋ชจ์๋ฆฌ๋ฅผ ์๋ก์ด ์์น๋ก ์ด๋์์ผ ๋ณํํ๋ฉด ํด๋น ์์ญ์ ์ด๋ฏธ์ง๊ฐ ์๊ทผ์ ์ผ๋ก ๋ณํ๋๋ค. ์ด ๋ณํ์ ์ํด 3x3 ํ๋ ฌ์ธ Homography matrix๋ฅผ ์ฌ์ฉํ๋ค.
OpenCV์์๋ cv2.getPerspectiveTransform() ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ์๊ทผ ๋ณํ ํ๋ ฌ์ ๊ณ์ฐํ๊ณ , cv2.warpPerspective() ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ๋ณํ๋ ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๋๋ฐ, ์ด ํจ์๋ค์ ๋ค์๊ณผ ๊ฐ์ ๋งค๊ฐ๋ณ์๋ฅผ ๊ฐ์ง๋ค.
- cv2.getPerspectiveTransform(src, dst): ์๊ทผ ๋ณํ ํ๋ ฌ์ ๊ณ์ฐ. src๋ ์ ๋ ฅ ์ด๋ฏธ์ง์์ ๊ด์ฌ ์์ญ์ ๋ชจ์๋ฆฌ 4๊ฐ์ ์ขํ์ด๊ณ , dst๋ ์ถ๋ ฅ ์ด๋ฏธ์ง์์ ํด๋น ๋ชจ์๋ฆฌ์ ์ขํ.
- cv2.warpPerspective(src, M, dsize): ์๊ทผ ๋ณํ๋ ์ด๋ฏธ์ง๋ฅผ ์์ฑ. src๋ ์ ๋ ฅ ์ด๋ฏธ์ง์ด๊ณ , M์ ์๊ทผ ๋ณํ ํ๋ ฌ์ ๋๋ค. dsize๋ ์ถ๋ ฅ ์ด๋ฏธ์ง ํฌ๊ธฐ.
์ฝ๋ ์์
- ๋ง์ฐ์ค ์ฝ๋ฐฑ์ ์ฌ์ฉํ์ฌ ์ ๋ ฅ ์ด๋ฏธ์ง์์ ๋ณํ ํ๋ ฌ ๊ณ์ฐ์ ์ฌ์ฉํ๊ณ ์ถ์ ๋ชจ์๋ฆฌ 4๊ณณ์ ํด๋ฆญ
- ํด๋ฆญํ ๋ชจ์๋ฆฌ 4๊ณณ์ผ๋ก ๋ณํ ํ๋ ฌ์ ๊ณ์ฐํ๊ณ ๋ณํ ํ๋ ฌ๋ก perspective transformation ๋ ์ด๋ฏธ์ง ์ถ๋ ฅ
import cv2
import numpy as np
win_name = "scanning"
img = cv2.imread("menu1.jpg")
rows, cols = img.shape[:2]
draw = img.copy()
pts_cnt = 0
pts = np.zeros((4, 2), dtype=np.float32)
def onMouse(event, x, y, flags, param):
global pts_cnt
if event == cv2.EVENT_LBUTTONDOWN:
# ์ขํ์ ์ด๋ก์ ๋๊ทธ๋ผ๋ฏธ ํ์
cv2.circle(draw, (x, y), 10, (0, 255, 0), -1)
cv2.imshow(win_name, draw)
# ๋ง์ฐ์ค ์ขํ ์ ์ฅ
pts[pts_cnt] = [int(x), int(y)]
pts_cnt += 1
if pts_cnt == 4:
# ์ขํ 4๊ฐ ์ค ์ํ์ข์ฐ ์ฐพ๊ธฐ
sm = pts.sum(axis=1) # 4์์ ์ขํ ๊ฐ๊ฐ x+y ๊ณ์ฐ
diff = np.diff(pts, axis=1) # 4์์ ์ขํ ๊ฐ๊ฐ x-y ๊ณ์ฐ
topLeft = pts[np.argmin(sm)] # x+y๊ฐ ๊ฐ์ฅ ๊ฐ์ด ์ข์๋จ ์ขํ
bottomRight = pts[np.argmax(sm)] # x+y๊ฐ ๊ฐ์ฅ ํฐ ๊ฐ์ด ์ฐํ๋จ ์ขํ
topRight = pts[np.argmin(diff)] # x-y๊ฐ ๊ฐ์ฅ ์์ ๊ฒ์ด ์ฐ์๋จ ์ขํ
bottomLeft = pts[np.argmax(diff)] # x-y๊ฐ ๊ฐ์ฅ ํฐ ๊ฐ์ด ์ขํ๋จ ์ขํ
# ๋ณํ ์ 4๊ฐ ์ขํ
pts1 = np.float32([topLeft, topRight, bottomRight, bottomLeft])
# ๋ณํ ํ ์์์ ์ฌ์ฉํ ์๋ฅ์ ํญ๊ณผ ๋์ด ๊ณ์ฐ
w1 = abs(bottomRight[0] - bottomLeft[0])
w2 = abs(topRight[0] - topLeft[0])
h1 = abs(topRight[1] - bottomRight[1])
h2 = abs(topLeft[1] - bottomLeft[1])
width = int(max([w1, w2])) # ๋ ์ข์ฐ ๊ฑฐ๋ฆฌ๊ฐ์ ์ต๋๊ฐ์ด ์๋ฅ์ ํญ
height = int(max([h1, h2])) # ๋ ์ํ ๊ฑฐ๋ฆฌ๊ฐ์ ์ต๋๊ฐ์ด ์๋ฅ์ ๋์ด
# ๋ณํ ํ 4๊ฐ ์ขํ
pts2 = np.float32([[0, 0], [width - 1, 0],
[width - 1, height - 1], [0, height - 1]])
# ๋ณํ ํ๋ ฌ ๊ณ์ฐ
mtrx = cv2.getPerspectiveTransform(pts1, pts2)
# ์๊ทผ ๋ณํ ์ ์ฉ
result = cv2.warpPerspective(img, mtrx, (width, height))
cv2.imshow('scanned', result)
cv2.imshow(win_name, img)
cv2.setMouseCallback(win_name, onMouse)
cv2.waitKey(0)
cv2.destroyAllWindows()