opencv で カードの向きを直してみる

チュートリアルを通じて学んだことを活かして、机の上においたカードを普通に(真上ではない角度から)撮影した画像を、まっすぐに修正してみる。

なお、カードが真っ白だったので、向きが分かるように、適当に文字を上に乗せました。(カードにペンでかけばよかったかな・・・)

f:id:pongsuke:20170525121936j:plain

試行錯誤の結果ですが、処理の流れは、、、

  1. 白を検出(HSVに変換します)

  2. 二値化

  3. 境界を検出する

  4. 境界を近傍する

  5. 4つ角の座標を得る

  6. 射影変換する

#!/usr/bin/env python
# -*- coding: utf-8 -*
import sys
import cv2
import numpy as np
from pprint import pprint


def main():
    image = cv2.imread('DSC_2848.2.jpg')
    #cv2.imshow('original', image)

    # リサイズ
    height, width ,depth    = image.shape
    image = cv2.resize(image, (width/4, height/4))
    imageOrg    = image
    cv2.imshow('resize', image)
    cv2.imwrite('50.resize.png', image)

    # HSVへ変換
    image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) 

    # 白抽出:凄く明るい場所
    threashhold_min = np.array([0,0,180], np.uint8)
    threashhold_max = np.array([255,255,255], np.uint8)
    image = cv2.inRange(image, threashhold_min, threashhold_max)

    # BGRへ変換
    # inRange で グレースケールされている
    image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) 
    cv2.imshow('inRange', image)
    cv2.imwrite('50.inRange.png', image)

    # ノイズ除去
    kernel = np.ones((9,9), np.uint8)
    image   = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel)
    image   = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel)
    cv2.imshow('removeNoise', image)
    cv2.imwrite('50.removeNoise.png', image)

    # 反転処理
    image   = 255 - image

    # 境界抽出
    gray_min = np.array([0], np.uint8)
    gray_max = np.array([128], np.uint8)
    threshold_gray = cv2.inRange(image, gray_min, gray_max)
    image, contours, hierarchy = cv2.findContours(threshold_gray,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

    # 最大面積を探す
    max_area_contour=-1
    max_area    = 0
    for contour in contours:
        area=cv2.contourArea(contour)
        if(max_area<area):
            max_area=area
            max_area_contour = contour
    #print(max_area_contour)

    # カラー化
    #image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGBA)

    contours    = [max_area_contour]
    cv2.drawContours(imageOrg, max_area_contour, -1, (0, 255, 0), 5)

    # 輪郭の近似
    epsilon = 0.01 * cv2.arcLength(max_area_contour,True)
    approx = cv2.approxPolyDP(max_area_contour,epsilon,True)
    #print(approx)
    if len(approx) == 4:
        cv2.drawContours(imageOrg, [approx], -1, (255, 0, 0), 3)

    cv2.imshow('findContours', imageOrg)
    cv2.imwrite('50.findContours.png', imageOrg)

    # ソートが必要かな?
    #pprint(approx)
    #approx= np.sort(approx,axis=1)
    #approx= np.sort(approx,axis=0)
    #pprint(approx)

    height, width ,depth    = imageOrg.shape

    # 射影変換
    pts1 = np.float32(approx)
    pts2 = np.float32([[600,200],[300,200],[300,350],[600,350]])
    pprint(pts1)
    pprint(pts2)
    M = cv2.getPerspectiveTransform(pts1,pts2)
    imageOrg = cv2.warpPerspective(imageOrg,M,(width,height))
    cv2.imshow('getPerspectiveTransform', imageOrg)
    cv2.imwrite('50.getPerspectiveTransform.png', imageOrg)

    cv2.waitKey(0)
    cv2.destroyAllWindows()
    return 0

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        pass

リサイズ f:id:pongsuke:20170525122256p:plain

白を検出して二値化 f:id:pongsuke:20170525122317p:plain

ノイズ除去 f:id:pongsuke:20170525122251p:plain

境界検出 f:id:pongsuke:20170525122303p:plain

射影変換 f:id:pongsuke:20170525122310p:plain

cv2.approxPolyDP で帰ってくる配列ですが、3番目の引数に True を指定したので、閉じたポリゴンを返してくれるはず。

closed – これが真の場合,近似された曲線は閉じたものになり(つまり,最初と最後の頂点が接続されます),そうでない場合は,開いた曲線になります.

ですが、並び順が固定なのか、変動するのか不明。

このプログラムでは、右上 > 左上 > 左下 > 右下 の順(右上スタートの反時計回り)でしたが、毎回そうなのか、どうなのか・・・。