๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ’ป Programming/Computer Vision

[OpenCV] Perspective Transformation (์›๊ทผ ๋ณ€ํ™˜) | ์™œ๊ณก๋œ ์˜์ƒ์„ ํŽด์ฃผ๋Š” ๋ฐฉ๋ฒ•

by ๋ญ…์ฆค 2023. 3. 29.
๋ฐ˜์‘ํ˜•
Geometric Transformation

Geometric Transformation ์˜ˆ์‹œ

์˜์ƒ์€ ๊ธฐํ•˜ํ•™์  ๋ณ€ํ™˜์„ ํ†ตํ•ด ๋‹ค์–‘ํ•œ ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜๋  ์ˆ˜ ์žˆ๋Š”๋ฐ ์ž์œ ๋„์— ๋”ฐ๋ผ translation, eclidean,similarity, affine, perspective(projective) ๋ณ€ํ™˜์œผ๋กœ ๋‚˜๋‰œ๋‹ค. ์ด ์ค‘์—์„œ perspective transformation์˜ ์ž์œ ๋„๊ฐ€ ๊ฐ€์žฅ ํฌ๋‹ค. ๋‹ค์‹œ ๋งํ•ด ๊ฐ€์žฅ ๋งŽ์€ ๋ณ€ํ˜•์„ ์ค„ ์ˆ˜ ์žˆ๋Š” ๋ณ€ํ™˜์ด๋ผ๋Š” ๋œป์ด๋‹ค. ๋‹ค์–‘ํ•œ ์ปดํ“จํ„ฐ ๋น„์ „ ํ”„๋กœ์ ํŠธ์—์„œ ์นด๋ฉ”๋ผ์˜ ๊ฐ๋„์— ๋”ฐ๋ผ ์™œ๊ณก๋˜๋Š” ๊ฐ์ฒด๋‚˜ ํ…์ŠคํŠธ ๋“ค์„ ์ •๋ฉด์œผ๋กœ ๋ฐ”๋ผ๋ณด๋Š” view๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด affine ๋˜๋Š” perspective transformation์ด ์‚ฌ์šฉ๋œ๋‹ค.

 

 

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()

 

์ขŒ : ์ž…๋ ฅ ์ด๋ฏธ์ง€, ์šฐ : ๋ณ€ํ™˜ ๊ฒฐ๊ณผ

๋ฐ˜์‘ํ˜•