Python/컴퓨터 비전

2024-07-18 마스크, 관심영역, 이진화, 이미지 유사도, 영상의 변환

nomad06 2024. 7. 24. 10:33

1. 마스크 연산

inRange() : 영상에서 지젇된 범위 안에 픽셀 선택
copyTo() : 마스크 연산을 지원하는 픽셀 값 복사 함수

 

 특정 범위의 픽셀 값을 추출

       
      cv2.inRange(영상, min값 (Hue, Saturation, Value) , max값 (Hue, Saturation, Value) )

 
예시) cv2.inRange(img, (50, 150, 0), (80, 255, 255))

Hue(색조)가 50에서 80 사이,
Saturation(채도)가 150에서 255 사이,
Value(명도)가 0에서 255 사이의 범위를 지정합니다.

 

 

 특정 HSV 색상 범위를 추출하여 이진 이미지를 생성


       
        import cv2

        img = cv2.imread('./candies.png')
        hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

        dst = cv2.inRange(hsv, (50, 150, 0), (80, 255, 255))

        cv2.imshow('img', img)
        cv2.imshow('dst', dst)
        cv2.waitKey()


 

 

 

 

 마스크를 사용하여지정된 부분을 출력 영상으로 복사


       
          cv2.copyTo(영상, 마스크, 출력영상)

 
 

 

 

 마스크 따기


       
            import cv2

            img = cv2.imread('./airplane.bmp')
            mask = cv2.imread('./mask_plane.bmp')
            dst = cv2.imread('./field.bmp')

            cv2.imshow('img', img)
            cv2.imshow('mask', mask)

            cv2.waitKey()


 

 

 

 

 이미지 합치기


       
            import cv2
           
            img = cv2.imread('./airplane.bmp')
            mask = cv2.imread('./mask_plane.bmp')
            dst = cv2.imread('./field.bmp')
           
            # dst가 없으면 새로 만들고
            # dst가 있으면 dst에 추가
            temp = cv2.copyTo(img, mask)
            cv2.copyTo(img, mask, dst)
           
            cv2.imshow('img', img)
            cv2.imshow('mask', mask)
            cv2.imshow('dst', dst)
            cv2.imshow('temp', temp)
           
            cv2.waitKey()

 
 

 

 

 

 영상에 크로마키로 다른 배경을 넣어보기


     
          import cv2

          cap1 = cv2.VideoCapture('./woman.mp4')
          cap2 = cv2.VideoCapture('./sea.mp4')

          w = round(cap1.get(cv2.CAP_PROP_FRAME_WIDTH))
          h = round(cap1.get(cv2.CAP_PROP_FRAME_HEIGHT))

          frame_cnt1 = round(cap1.get(cv2.CAP_PROP_FRAME_COUNT))
          frame_cnt2 = round(cap2.get(cv2.CAP_PROP_FRAME_COUNT))
          fps = round(cap1.get(cv2.CAP_PROP_FPS))

          print(w)
          print(h)
          print(frame_cnt1)
          print(frame_cnt2)
          print(fps)

          while True:
              ret1, frame1 = cap1.read()
              if not ret1:
                  break
              ret2, frame2 = cap2.read()
              if not ret2:
                  break

              hsv = cv2.cvtColor(frame1, cv2.COLOR_BGR2HSV)
              mask = cv2.inRange(hsv, (50, 150, 0), (70, 255, 255))
              cv2.copyTo(frame2, mask, frame1)

              cv2.imshow('frame1', frame1)
              key = cv2.waitKey(10)
              if key == ord(' '):
                  cv2.waitKey()
              elif key == 27:
                  break

          cap1.release()
          cap2.release()

 
 



 

2. 관심 영역 (ROI, Region of Interest)

- 영상 내에서 관심이 있는 영역

 

 관심 영역을 선택하기


       
         cv2.selectROI(창이름, 영상, 중안좌표여부 = False)




  • 창이름: ROI를 선택할 때 나타날 창의 이름입니다. 선택할 영역을 표시할 창이 열립니다.
  • 영상: ROI를 선택할 이미지나 비디오 프레임입니다.
  • 중앙좌표여부 (optional): 기본적으로 False이며, True로 설정하면 ROI의 중심 좌표를 반환합니다.

 

 

 관심 영역을 추출하기


       
            import cv2

            img1 = cv2.imread('./sun.jpg')

            # x, y, w, h
            x = 182
            y = 21
            w = 122
            h = 110

            roi = img1[y: y+h, x: x+w]  # 이미지에서 ROI 영역을 슬라이싱
            img2 = roi.copy()  # ROI 영역을 새로운 이미지로 복사

            cv2.imshow('img1', img1) # 원본 이미지
            cv2.imshow('img2', img2) # ROI 이미지

            cv2.waitKey()


 
 

 

 

 

 드래그한 곳을 추출하기


       

        import cv2

        isDrag = False
        oldx = oldy = w = h = 0
        color = (255, 0, 0)
        img_copy = None

        def on_mouse(event, x, y, flags, param):
            global oldx, oldy, isDrag, img_copy
            if event == cv2.EVENT_LBUTTONDOWN:
                isDrag = True
                oldx = x
                oldy = y
            elif event == cv2.EVENT_MOUSEMOVE:
                if isDrag:
                    img_copy = img.copy()
                    cv2.rectangle(img_copy, (oldx, oldy), (x, y), color, 3)
                    cv2.imshow('img', img_copy)
            elif event == cv2.EVENT_LBUTTONUP:
                if isDrag:
                    isDrag = False
                    if x > oldx and y > oldy:
                        w = x - oldx
                        h = y - oldy
                        if w > 0 and h > 0:
                            cv2.rectangle(img_copy, (oldx, oldy), (x, y), color, 3)
                            cv2.imshow('img', img_copy)
                            roi = img[oldy: oldy + h, oldx: oldx + w]
                            cv2.imshow('roi', roi)
                    else:
                        cv2.imshow('img', img)
                        print('영역이 잘 못 되었음')


        img = cv2.imread('./sun.jpg')
        cv2.namedWindow('img')
        cv2.setMouseCallback('img', on_mouse)
        cv2.imshow('img', img)
        cv2.waitKey()



 

 

 

 

 

 cv2.selectROI 함수를 사용해보기

       
 

        import cv2

        img = cv2.imread('./sun.jpg')

        x, y, w, h = cv2.selectROI('img', img, False)

        if w and h:
            roi = img[y: y+h, x: x+w]
            cv2.imshow('roi', roi)

        cv2.waitKey()


 

 

 

 

 

1. 영상의 이진화(Binarization)

- 픽셀을 검은색 또는 횐색과 같이 두 개의 값으로 나누는 작업
- 영상에서 의미있는 관심영역(ROI)과 비 관심영역 구분할 때 사용
- 배경과 객체를 나울 때도 사용
- 영상의 이진화 연산을 할 때 나누는 특정값을 임계값이라고 함

 

 

 이미지의 이진화 처리

       

      cv2.threshold(영상, 임계값, 최대값, 플래그)

      cv2.THRESH_BINARY: 픽셀값이 임계값을 넘으면 최대값으로 지정하고 넘지 못하면 0으로 지정
      cv2.THRESH_BINARY_INV: THRESH_BINARY 의 반대

 
 

 

 

 예제 : 흑백이미지 이진화 처리


       
        import cv2
        import matplotlib.pyplot as plt

        img = cv2.imread('./cells.png', cv2.IMREAD_GRAYSCALE)
        hist = cv2.calcHist([img], [0], None, [256], [0, 256])

        # cv2.THRESH_BINARY: 픽셀값이 임계값을 넘으면 최대값으로 지정하고 넘지 못하면 0으로 지정
        a, dst1 = cv2.threshold(img, 100, 255, cv2.THRESH_BINARY)
        b, dst2 = cv2.threshold(img, 210, 255, cv2.THRESH_BINARY)
        print('a:', a)

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

        plt.plot(hist)
        plt.show()
        cv2.waitKey()


 

 

 

 

2. 오츠의 이진화 알고리즘

- 자동 이진화
- 자동으로 임계값을 구하는 알고리즘, 임계값을 구분하는 가장 좋은 방법으로 사용

 

 Otsu's 방법을 통해 자동으로 최적의 임계값을 선택


       
         cv2.threshold(영상, 임계값, 최대값, 플래그 | cv2.THRESH_OTSU)


- 임계값을 임의로 정해 픽셀을 두 분류로 나누고 
두 분류의 명암 분포를 구하는 작업을 반복하여 모든 경우의 수 중에서 두 분류의 명암 분류가 가장 균일할 때의 임의값을 선택

 

 

 예제 : Otsu's 방법을 통해 이진화


       
        import cv2

        img = cv2.imread('./rice.png', cv2.IMREAD_GRAYSCALE)

        th, dst = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

        print('otsh: ', th)

        cv2.imshow('img', img)
        cv2.imshow('dst', dst)

        cv2.waitKey()

 
 

 

 

 

3. 지역 이진화

- 균일하지 않은 조명 환경에서 사용하는 이진화 방법
- 전체 구역에 N등분하고 각각의 디역에 이진화를 한 뒤에 이어 붙이는 방법
- 여러개의 임계값을 이용할 수 있음

 

 rice.png 이미지를 가로와 세로로 각각 4등분하여
     전역 이진화와 지역 이진화를 적용하기


       
      # rice.png 영상을 이용하여 가로 4등분, 세로 4등분하고 자동 이진화를 적용해보자.
      # 전역(자동) 이진화와 비교
      import cv2
      import numpy as np

      img = cv2.imread('./rice.png', cv2.IMREAD_GRAYSCALE)

      # 전역 이진화
      _, dst1 = cv2.threshold(img, 0,255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

      # 지역 이진화
      dst2 = np.zeros(img.shape, np.uint8)
      bw = img.shape[1] # 4
      bh = img.shape[0] # 4

      # 4x4 영역으로 나누어 지역 이진화 수행
      for x in range(4):
          for y in range(4):
              img_ = img[y*bh: (y+1)*bh, x*bw: (x+1)*bw]
              dst_ = dst2[y*bh: (y+1)*bh, x*bw: (x+1)*bw]
              # 지역 이진화 수행
              cv2.threshold(img_, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU, dst_)

      cv2.imshow('img', img)
      cv2.imshow('dst1', dst1)
      cv2.imshow('dst2', dst2)
      cv2.waitKey()


 
 

 

 

 

4. 적응형 이진화

- 영상을 여러 영역으로 나눈뒤, 그 주변 픽셀 값만 활요하여 임계값을 구함
- 노이즈를 제거한 뒤에 Otsu 이잔화를 적용

 

 이미지에 적응형 임계값을 적용하기


       
         cv2.adaptiveThreshold (영상, 임계값을 만족하는 픽셀에 적용할 값, 임계값 결정 방법, Threshold 적용 방법, 블록 사이즈, 가감할 상수)


         cv2.ADAPTIVE_THRESH_MEAN_C: 이웃 픽셀의 평균으로 결정 -> 선명하지만 잡티가 많아짐

         cv2.ADAPTIVE_THRESH_GAISSIAN_C: 가우기안 분포에 따른 가중치의 합으로 결정 -> 선명도는 조금 떨어지지만 잡티가 적음


  • 영상 (src): 입력 이미지 (단일 채널, 보통 그레이스케일 이미지)
  • 임계값을 만족하는 픽셀에 적용할 값 (maxValue): 임계값을 넘는 픽셀에 적용할 최대 값 (일반적으로 255)
  • 임계값 결정 방법 (adaptiveMethod):
    • 적응형 임계값 결정 방법을 지정합니다.
    • cv2.ADAPTIVE_THRESH_MEAN_C: 이웃 픽셀의 평균을 사용하여 임계값을 결정합니다.
    • cv2.ADAPTIVE_THRESH_GAUSSIAN_C: 가우시안 윈도우의 가중치 합을 사용하여 임계값을 결정합니다.
  • Threshold 적용 방법 (thresholdType): 이진화 결과를 적용할 방법을 지정합니다.
    • cv2.THRESH_BINARY: 임계값을 넘으면 maxValue를 할당하고, 그렇지 않으면 0을 할당합니다.
    • cv2.THRESH_BINARY_INV: 임계값을 넘으면 0을 할당하고, 그렇지 않으면 maxValue를 할당합니다.
  • 블록 사이즈 (blockSize): 임계값을 계산하기 위한 블록 크기를 지정합니다. 블록 사이즈는 이웃 픽셀들의 영역 크기를 말합니다.
    • 3 이상의 값
    • 블록 사이즈가 클수록 연산 시간이 오래 걸림
  • 가감할 상수 (C):평균이나 가중치 평균에서 빼거나 더할 상수를 지정합니다. 이 값은 계산된 임계값에 더해지거나 뺄 수 있습니다.

 

 

 sudoku.jpg 이미지에 대해 전역 이진화와 적응형 이진화를 비교하여 결과를 시각화


     
          import cv2
          import matplotlib.pyplot as plt

          img = cv2.imread('./sudoku.jpg', cv2.IMREAD_GRAYSCALE)

          # 전역 이진화 (OTSU 알고리즘 사용)
          th, dst1 = cv2.threshold(img, 0 ,255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

          # 적응형 이진화 (평균 기반)
          dst2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 5)

          # 적응형 이진화 (가우시안 가중치 기반)
          dst3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 9, 5)

          # 결과 이미지 저장
          dic = {'img': img, 'dst1': dst1, 'dst2': dst2, 'dst3': dst3}

          for i, (k, v) in enumerate(dic.items()):
              plt.subplot(2, 2, i+1) # 2x2의 subplot 중 i+1 번째에 배치
              plt.title(k) # 제목 설정
              plt.imshow(v, 'gray') # 이미지 표시 (흑백으로)

          plt.show()
 
 

 

 

 


1. 이미지 유사도

- 픽셀 값의 분표가 서로 비슷하다면 유사한 이미지일 확률이 높음

 

🔵 비교 알고리즘

       

        cv2.compareHist(히스트그램1, 히스트그램2, 알고리즘)

        cv2.HISTCMP_CORREL: 상관관계(1: 완정 일치, -1: 완전 불일치, 0: 무관계)
 
        cv2.HISTCMP_CHISQR: 카이관계(0: 완전 일치, 무한대: 완전 불일치)

       cv2.HISTCMP_INTERSECT: 교차(1: 완전 일치, 0: 완전 불일치)
 
       cv2.BHATTACHARYYA: 밀도함수(0: 완정 일치, 1: 완정 불일치)


 

 

 

 예제 : 여러 이미지 간의 히스토그램 유사도를 비교하기



          
import cv2
          import matplotlib.pyplot as plt
          import numpy as np

          img1 = cv2.imread('./taekwonv1.jpg')
          img2 = cv2.imread('./taekwonv2.jpg')
          img3 = cv2.imread('./taekwonv3.jpg')
          img4 = cv2.imread('./dr_ochanomizu.jpg')

          imgs = [img1, img2, img3, img4]
          hists = []

          # 각 이미지에 대해 히스토그램 계산 및 비교를 수행
          for i, img in enumerate(imgs):
              plt.subplot(1, len(imgs), i+1)
              plt.title('img%d' % (i+1))
              plt.axis('off')
              plt.imshow(img[:, :, ::-1])
              hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
              hist = cv2.calcHist([hsv], [0, 1], None, [180,256], [0, 180, 0, 256])
              cv2.normalize(hist, hist, 0, 1, cv2.NORM_MINMAX)
              hists.append(hist)


          query = hists[0]
          methods = {'CORREL': cv2.HISTCMP_CORREL, 'CHISQR': cv2.HISTCMP_CHISQR, 'INTERSECT': cv2.HISTCMP_INTERSECT, 'BHATTACHARYYA': cv2.HISTCMP_BHATTACHARYYA}

          # 각 메소드별로 히스토그램 유사도를 계산하고 출력
          for j, (name, flag) in enumerate(methods.items()):
              print('%-10s' % name, end = '\t')

              for i, (hist, img) in enumerate(zip(hists, imgs)):
                  ret = cv2.compareHist(query, hist, flag)
                  # INTERSECT 메소드의 경우 정규화된 결과를 사용하여 출력
                  if flag == cv2.HISTCMP_INTERSECT:
                      ret = ret/np.sum(query)

                  print('img%d:%7.2f' % (i+1, ret), end = '\t')

              print()

          plt.show()

      
 

 

 

2. 영상의 변환

- 영상을 구상하는 픽셀의 배치 구조를 변경함으로 전체 영상의 모양을 바꾸는 작업

 

🔵이미지 이동(translate)


       
         M = [1 0 a]
                 [0 1 b]

         x 방향으로 a 만큼
, y 방향으로 b 만큼 이동하는 행렬

         cv2.warpaffine(영상, 2 * 3 변환행렬, 결과, 보간법 알고리즘)


이미지 이동(translate) : 
(0,0)을 매게변수로 전달하면 입력 영상과 같은 행렬을 반환
원래 있던 좌표에 이동시키려는 거리 만큼 연산(shift)

 

🔵보간법 일고리즘(interpolation)



      cv2.INTER_LINEAR: 인접한 4개의 픽셀 값에 거리 가중치 사용 -> 속도는 빠르지만 퀄리티가 좀 떨어짐
      cv2
.INTRE_NEAREST: 가장 가까운 픽셀 값 사용 -> 속도가 가장 빨르지만 퀄리티가 떨어짐
      cv2
.INTER_AREA: 픽셀 영역 관계를 이용한 재샘플링 -> 영역적인 정보를 추출해서 결과 영상을 세팅하는 방법, 다운샘플링시 효과적
      cv2
.INTER_CUBIC: 인접한 16개의 픽셀 값에 사중치를 사용 -> 퀄리티는 가장 촣치만 속도가 떨어짐

 
 

 

 예제: 이미지 이동  / cv2.warpaffine() 사용하기


       
        import cv2
        import numpy as np

        img = cv2.imread('../Day1/dog.bmp')

        # [1, 0, a], [0, 1, b]
        aff = np.array([[1, 0, 150], [0, 1, 100]], dtype=np.float32)
        dst = cv2.warpAffine(img, aff, (0, 0))

        cv2.imshow('img', img)
        cv2.imshow('dst', dst)
        cv2.waitKey()


 



🔵이미지 크기 변환 (resize)


       

               cv2.resize(영상, 결과, x와 y방향 스케일 비율, 보간법)


영상의 크기를 원본 영상보다 크게 또는 작게 만드는 변환

 

 

 

 예제: 이미지 확대하기 / cv2.INTER_NEAREST, cv2.INTER_CUBIC 사용하기


       
        import cv2

        img = cv2.imread('../Day1/dog.bmp')
        dst1 = cv2.resize(img, (1280, 1024), interpolation=cv2.INTER_NEAREST)
        dst2 = cv2.resize(img, (1280, 1024), interpolation=cv2.INTER_CUBIC)

        cv2.imshow('img', img)
        cv2.imshow('dst1', dst1[400:800, 200:600])
        cv2.imshow('dst2', dst1[400:800, 200:600])

        cv2.waitKey()


 

 

🔵 회전(rotation) 


       
            cv2.getRotationMatrix2D(중심 좌표, 회전각도, 확대비율) -> affine 행렬


영상의 특정 각도 만큼 회전시키는 변환(반시계 방향)

* 회전각도: 반시계방향(기본값), 음수는 시계방향
* 확대비율: 0~1 사이의 실수

 

 예제: 이미지 회전시키기


       
        import cv2

        img = cv2.imread('../Day1/dog.bmp')
        cp = (img.shape[1] / 2, img.shape[0] / 2)

        rot = cv2.getRotationMatrix2D(cp, 30, 0.5)
        dst = cv2.warpAffine(img, rot, (0, 0))

        cv2.imshow('img', img)
        cv2.imshow('dst', dst)
        cv2.waitKey()
 
 

 

 

🔵투시변환(perspective)

       

        cv2.getPerspectiveTransforms(영상, 4개의 결과 좌표점) -> 투시 변환 행렬
        cv2
.wrapPerspective(영상, 투시 변환 행렬, 결과 영상 크기)

 
- 직사각형 형태의 영상을 임의의 입체감 있는 사각형 형태로 변경할 수 있는 변환
- 원본 영상에 있는 직선은 결과 영상에서 그대로 유지 되지 않고 평행 관계가 깨질 수 있음
- 투시 변환은 보통 3*3 크기의 실수 행렬로 표현
- 8개의 파라미터로 표현할 수 있지만, 좌표 계산 편의상 9개의 원소를 갖는 행렬로 표현

 

 

 예제 : 이미지 투시변환하기



        import cv2
        import numpy as np

        img = cv2.imread('./pic.jpg')
        w, h = 600, 400

        srcQuad = np.array([[370, 173], [1220, 155], [1420, 840], [210, 850]], np.float32)
        dstQuad = np.array([[0, 0], [w, 0], [w, h], [0, h]], np.float32)

        pers = cv2.getPerspectiveTransform(srcQuad, dstQuad)
        dst = cv2.warpPerspective(img, pers, (w, h))

        cv2.imshow('img', img)
        cv2.imshow('dst', dst)
        cv2.waitKey()


 

 

 

 

 방법1


       
        import cv2
        import numpy as np

        # 전역 변수 선언
        points = []
        dragging_point_index = -1
        image_path = './namecard.jpg'

        # 마우스 이벤트 핸들러 함수
        def mouse_handler(event, x, y, flags, param):
            global points, image, dragging_point_index
            if event == cv2.EVENT_LBUTTONDOWN:
                for i, point in enumerate(points):
                    if abs(point[0] - x) < 10 and abs(point[1] - y) < 10:
                        dragging_point_index = i
                        break
            elif event == cv2.EVENT_MOUSEMOVE:
                if dragging_point_index != -1:
                    points[dragging_point_index] = (x, y)
                    redraw_image()
            elif event == cv2.EVENT_LBUTTONUP:
                dragging_point_index = -1

        def redraw_image():
            global image
            image = orig_image.copy()
            for i, point in enumerate(points):
                cv2.circle(image, point, 10, (255, 0, 255), -1)  # 큰 핑크색 점
                if i > 0:
                    cv2.line(image, points[i-1], point, (255, 0, 255), 2)  # 핑크색 선
            if len(points) == 4:
                cv2.line(image, points[3], points[0], (255, 0, 255), 2)  # 마지막 점과 첫 점을 연결하는 선
            cv2.imshow("Image", image)

        # 이미지를 불러오고 초기화
        image = cv2.imread(image_path)
        orig_image = image.copy()

        # 초기 네 점 설정 (이미지의 모서리 근처에 배치)
        h, w, _ = orig_image.shape
        points = [(50, 50), (w - 50, 50), (w - 50, h - 50), (50, h - 50)]

        redraw_image()
        cv2.setMouseCallback("Image", mouse_handler)
        print("이미지에서 네 점을 선택하고 움직이세요. Enter 키를 누르면 완료됩니다.")

        while True:
            key = cv2.waitKey(1) & 0xFF
            if key == 13:  # Enter 키를 누르면 변환된 이미지를 새 창에 표시
                if len(points) == 4:
                    # 원본 이미지의 크기를 얻음
                    h, w, _ = orig_image.shape

                    # 선택한 네 점
                    pts1 = np.float32(points)

                    # 대상 좌표 설정 (직사각형으로 변환)
                    pts2 = np.float32([[0, 0], [w, 0], [w, h], [0, h]])

                    # 변환 행렬 계산
                    matrix = cv2.getPerspectiveTransform(pts1, pts2)

                    # 원근 변환 적용
                    result = cv2.warpPerspective(orig_image, matrix, (w, h))

                    # 결과 이미지 표시
                    cv2.imshow("Transformed Image", result)
                    print("변환된 이미지가 새 창에 표시됩니다.")
                else:
                    print("네 점이 선택되지 않았습니다.")
            elif key == 27:  # Esc 키를 누르면 종료
                break

        cv2.destroyAllWindows()




 

 

 

 방법2


       
      import cv2
      import numpy as np
      import sys

      img = cv2.imread('./namecard.jpg')
      h, w = img.shape[:2]
      dh = 500
      # A4용지 크기: 210*297mm
      dw = round(dh * 297 / 210)

      srcQuad = np.array([[30, 30], [30, h-30], [w-30, h-30], [w-30, 30]], np.float32)
      dstQuad = np.array([[0, 0], [0, dh], [dw, dh], [dw, 0]], np.float32)

      dragSrc = [False, False, False, False]

      def drawROI(img, corners):
          cpy = img.copy()
          c1 = (192, 192, 255)
          c2 = (128, 128, 255)

          for pt in corners:
              cv2.circle(cpy, tuple(pt.astype(int)), 25, c1, -1)

          cv2.line(cpy, tuple(corners[0].astype(int)), tuple(corners[1].astype(int)), c2, 2)
          cv2.line(cpy, tuple(corners[1].astype(int)), tuple(corners[2].astype(int)), c2, 2)
          cv2.line(cpy, tuple(corners[2].astype(int)), tuple(corners[3].astype(int)), c2, 2)
          cv2.line(cpy, tuple(corners[3].astype(int)), tuple(corners[0].astype(int)), c2, 2)

          return cpy

      def onMouse(event, x, y, flags, param):
          global srcQuad, dragSrc, ptOld, img

          if event == cv2.EVENT_LBUTTONDOWN:
              for i in range(4):
                  if cv2.norm(srcQuad[i] - (x, y)) < 25:
                      dragSrc[i] = True
                      ptOld = (x, y)
                      break

          if event == cv2.EVENT_LBUTTONUP:
              for i in range(4):
                  dragSrc[i] = False

          if event == cv2.EVENT_MOUSEMOVE:
              for i in range(4):
                  if dragSrc[i]:
                      srcQuad[i] = (x, y)
                      cpy = drawROI(img, srcQuad)
                      cv2.imshow('img', cpy)
                      ptOld = (x, y)
                      break


      disp = drawROI(img, srcQuad)
      cv2.namedWindow('img')
      cv2.setMouseCallback('img', onMouse)
      cv2.imshow('img', disp)

      while True:
          key = cv2.waitKey()
          if key == 13:
              break
          elif key == 27:
              sys.exit()

      pers = cv2.getPerspectiveTransform(srcQuad, dstQuad)
      dst = cv2.warpPerspective(img, pers, (dw, dh), flags=cv2.INTER_CUBIC)
      cv2.imshow('dst', dst)
      cv2.waitKey()