opencv で マーカー付き用紙の向きを直してみる

マーカーの検出を利用して、向きを直してみます。

カメラの歪み補正等(キャリブレーション)には踏み込みません。

四隅に黒い円のマーカーを付けた用紙です。

f:id:pongsuke:20170531161053j:plain

この傾きを補正しようと思います。

流れは

  1. マーカー検出

  2. 座標の並び替え

  3. 射影変換

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

"""
円検出と、射影変換
"""
def main():
    image = cv2.imread('DSC_3009.jpg')
    #cv2.imshow('original', image)

    height, width ,depth    = image.shape
    # リサイズ
    image = cv2.resize(image, (width/4, height/4))
    imageOrg    = image.copy()
    height, width ,depth    = image.shape

    # グレースケールに変換
    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # ガウシアンフィルタ
    image = cv2.GaussianBlur(image, (5, 5), 0)
    # 二値変換
    ret, image = cv2.threshold(image,127,255,cv2.THRESH_BINARY)

    # Python: cv2.findCirclesGridDefault(image, patternSize[, centers[, flags]]) → retval, centers
    # CALIB_CB_SYMMETRIC_GRID uses symmetric pattern of circles.
    # CALIB_CB_CLUSTERING uses a special algorithm for grid detection. It is more robust to perspective distortions but much more sensitive to background clutter.
    retval, centers = cv2.findCirclesGrid(image, (2,2), flags=cv2.CALIB_CB_SYMMETRIC_GRID + cv2.CALIB_CB_CLUSTERING)
    if retval:
        print centers
        image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
        image = cv2.drawChessboardCorners(image, (2,2), centers, retval)

    # 射影変換のために、並び替えた配列を用意する
    points  = np.array([
        centers[0],
        centers[2],
        centers[3],
        centers[1]
    ])
    print points

    # 外接矩形
    bRect = cv2.boundingRect(points)
    print bRect
    cv2.rectangle(image, (bRect[0], bRect[1]), (bRect[0]+bRect[2], bRect[1]+bRect[3]), (255,0,0), 2)
    cv2.imshow('drawChessboardCorners', image)
    cv2.imwrite('90.drawChessboardCorners.png', image)

    # 射影変換
    # 右上、左上、左下、右下
    pts1 = np.float32(points)
    pts2 = np.float32([
        [bRect[0]+bRect[2],bRect[1]],
        [bRect[0],bRect[1]],
        [bRect[0],bRect[1]+bRect[3]],
        [bRect[0]+bRect[2],bRect[1]+bRect[3]],
    ])
    print(pts1)
    print(pts2)
    M = cv2.getPerspectiveTransform(pts1,pts2)
    imageOrg = cv2.warpPerspective(imageOrg,M,(width,height))
    cv2.imshow('getPerspectiveTransform', imageOrg)
    cv2.imwrite('90.getPerspectiveTransform.png', imageOrg)

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

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

検出された円と、外接矩形
f:id:pongsuke:20170531161530p:plain

完了
f:id:pongsuke:20170531161558p:plain

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

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

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

opencv チュートリアルチャンレンジ 82 画像のInpainting

画像のInpainting — OpenCV-Python Tutorials 1 documentation

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

img = cv2.imread('170519-144402.cut.jpg')
mask = cv2.imread('170519-144402.mask.jpg',0)

# INPAINT_TELEA
dst = cv2.inpaint(img, mask, 3, cv2.INPAINT_TELEA)
cv2.imshow('inpaint.INPAINT_TELEA',dst)
cv2.imwrite('inpaint.INPAINT_TELEA.png',dst)

# INPAINT_NS
dst = cv2.inpaint(img, mask, 3, cv2.INPAINT_NS)
cv2.imshow('inpaint.INPAINT_NS',dst)
cv2.imwrite('inpaint.INPAINT_NS.png',dst)

cv2.waitKey(0)
cv2.destroyAllWindows()

やってみる

f:id:pongsuke:20170524144814j:plain

マスク f:id:pongsuke:20170524144824j:plain

処理結果 INPAINT_TELEA f:id:pongsuke:20170524144855p:plain

処理結果 INPAINT_NS f:id:pongsuke:20170524145141p:plain

流石に大きすぎたかな?

f:id:pongsuke:20170524145533j:plain

f:id:pongsuke:20170524145536j:plain

f:id:pongsuke:20170524145604p:plain

f:id:pongsuke:20170524145608p:plain

opencv チュートリアルチャンレンジ 81 画像のノイズ除去

画像のノイズ除去 — OpenCV-Python Tutorials 1 documentation

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

img = cv2.imread('170523-170301.jpg')
#img = cv2.imread('170519-174830.jpg')
#img = cv2.imread('170519-144402.jpg')
#img = cv2.imread('chessboard.jpg')

cv2.imshow('goodFeaturesToTrack',img)

dst = cv2.fastNlMeansDenoisingColored(img,None,10,10,7,21)
cv2.imshow('fastNlMeansDenoisingColored',dst)
cv2.imwrite('fastNlMeansDenoisingColored.png',dst)

cv2.waitKey(0)
cv2.destroyAllWindows()

やってみる

f:id:pongsuke:20170523172643j:plain

f:id:pongsuke:20170524144138p:plain

opencv チュートリアルチャンレンジ 43 Shi-Tomasiのコーナー検出とGood Features to Track(追跡に向いた特徴)

Shi-Tomasiのコーナー検出とGood Features to Track(追跡に向いた特徴) — OpenCV-Python Tutorials 1 documentation

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

img = cv2.imread('170523-170301.jpg')
#img = cv2.imread('170519-174830.jpg')
#img = cv2.imread('170519-144402.jpg')
#img = cv2.imread('chessboard.jpg')

gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

corners = cv2.goodFeaturesToTrack(gray, 25, 0.01, 10)
corners = np.int0(corners)

for i in corners:
    x,y = i.ravel()
    cv2.circle(img, (x,y), 3, [0,0,255], -1)

cv2.imshow('goodFeaturesToTrack',img)
cv2.imwrite('goodFeaturesToTrack.png',img)

cv2.waitKey(0)
cv2.destroyAllWindows()

f:id:pongsuke:20170524140823p:plain

opencv チュートリアルチャンレンジ 42 Harrisコーナー検出

Harrisコーナー検出 — OpenCV-Python Tutorials 1 documentation

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

img = cv2.imread('170523-170301.jpg')
#img = cv2.imread('170519-174830.jpg')
#img = cv2.imread('170519-144402.jpg')
#img = cv2.imread('chessboard.jpg')

gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

gray = np.float32(gray)
#dst = cv2.cornerHarris(gray, 2, 3, 0.04)
dst = cv2.cornerHarris(gray, 4, 5, 0.04)

#result is dilated for marking the corners, not important
dst = cv2.dilate(dst, None)

tmp = np.zeros(dst.shape[:2], np.uint8)
tmp[ dst > 0.01*dst.max() ] = [255]
tmp[ dst <= 0.01*dst.max() ] = [0]
cv2.imshow('cornerHarrisMask',tmp)
cv2.imwrite('cornerHarrisMask.png',tmp)

# Threshold for an optimal value, it may vary depending on the image.
img[ dst > 0.01*dst.max() ] = [0,0,255]

cv2.imshow('cornerHarris',img)
cv2.imshow('cornerHarris.png',img)

cv2.waitKey(0)
cv2.destroyAllWindows()

Harrisのコーナー検出を適用した結果は各画素が上記のスコア( R )を表すグレースケール画像になります.適切な閾値処理を施すと,画像中のコーナーを検出できます.

やってみる

cv2.cornerHarris で出てきた結果をゴニョゴニョ(適切な閾値処理)した結果を、コーナーとみなすらしい。

元画像 f:id:pongsuke:20170524131853j:plain

cv2.cornerHarris の結果 f:id:pongsuke:20170524133030p:plain

cv2.cornerHarris の閾値処理後 f:id:pongsuke:20170524133041p:plain

適用結果 f:id:pongsuke:20170524133048p:plain

opencv チュートリアルチャレンジ14 ハフ変換による円検出

ハフ変換による円検出 — OpenCV-Python Tutorials 1 documentation

void HoughCircles(Mat& image, vector<Vec3f>& circles, int method, double dp, double minDist, double param1=100, double param2=100, int minRadius=0, int maxRadius=0)
ハフ変換を用いて,グレースケール画像から円を検出します.

パラメタ:   
image – 8ビット,シングルチャンネル,グレースケールの入力画像.
circles – 検出された円を出力するベクトル.各ベクトルは,3要素の浮動小数点型ベクトル  (x, y, radius) としてエンコードされます.
method – 現在のところ, CV_HOUGH_GRADIENT メソッドのみが実装されています.基本的には 2段階ハフ変換 で,これについては Yuen90 で述べられています.
dp – 画像分解能に対する投票分解能の比率の逆数.例えば, dp=1 の場合は,投票空間は入力画像と同じ分解能をもちます.また dp=2 の場合は,投票空間の幅と高さは半分になります.
minDist – 検出される円の中心同士の最小距離.このパラメータが小さすぎると,正しい円の周辺に別の円が複数誤って検出されることになります.逆に大きすぎると,検出できない円がでてくる可能性があります.
param1 – 手法依存の 1 番目のパラメータ. CV_HOUGH_GRADIENT の場合は, Canny() エッジ検出器に渡される2つの閾値の内,大きい方の閾値を表します(小さい閾値は,この値の半分になります).
param2 – 手法依存の 2 番目のパラメータ. CV_HOUGH_GRADIENT の場合は,円の中心を検出する際の投票数の閾値を表します.これが小さくなるほど,より多くの誤検出が起こる可能性があります.より多くの投票を獲得した円が,最初に出力されます.
minRadius – 円の半径の最小値.
maxRadius – 円の半径の最大値.

やってみよう

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

cimg = cv2.imread('170523-170301.jpg')
img = cv2.cvtColor(cimg,cv2.COLOR_BGR2GRAY)
img = cv2.medianBlur(img,15)

circles = cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,1,20, param1=50,param2=30,minRadius=0,maxRadius=0)
circles = np.uint16(np.around(circles))

for i in circles[0,:]:
    # draw the outer circle
    cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
    # draw the center of the circle
    cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3)

cv2.imshow('HoughCircles',cimg)
cv2.imwrite('HoughCircles.png',cimg)

cv2.waitKey(0)
cv2.destroyAllWindows()

これが f:id:pongsuke:20170523172643j:plain

こう f:id:pongsuke:20170523172652p:plain

なんだろうか、この検出結果は。

よくみると、複数の光源によって、ゴルフボールに複数の影ができていて、その影を検出しているようだ。

また、テカリ?も検出しているようにみえる。