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

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

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

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

opencv チュートリアルチャレンジ12 テンプレートマッチング

テンプレートマッチング — OpenCV-Python Tutorials 1 documentation

void matchTemplate(const Mat& image, const Mat& templ, Mat& result, int method)
テンプレートと,それに重なった画像領域とを比較します.

パラメタ:   
image – テンプレートの探索対象となる画像.8ビットまたは32ビットの浮動小数点型.
templ – 探索されるテンプレート.探索対象となる画像以下のサイズで,同じデータ型でなければいけません.
result – 比較結果のマップ.シングルチャンネル,32ビット,浮動小数点型. image が  W x H で, templ が  w x h とすると result は  (W-w+1) x (H-h+1) となります.
method – 比較手法の指定(以下の説明を参照してください).

やってみる

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

imgOrg = cv2.imread('mario.png')
img = cv2.cvtColor(imgOrg, cv2.COLOR_BGR2GRAY)

template = cv2.imread('mario.coin.png',0)
w, h = template.shape[::-1]

res = cv2.matchTemplate(img,template,cv2.TM_CCOEFF_NORMED)
threshold = 0.99
loc = np.where( res >= threshold)
for pt in zip(*loc[::-1]):
    cv2.rectangle(imgOrg, pt, (pt[0] + w, pt[1] + h), (0,0,255), 1)

cv2.imshow('matchTemplate',imgOrg)
cv2.imwrite('matchTemplate.png',imgOrg)

cv2.waitKey(0)
cv2.destroyAllWindows()

検索対象画像
f:id:pongsuke:20170523163827p:plain

テンプレート画像
f:id:pongsuke:20170523163838p:plain

マッチ結果
f:id:pongsuke:20170523163831p:plain

なんというか、ほぼぴったり一致しないとヒットしない。

「顔」のような抽象的な検索ではない。

opencv チュートリアルチャレンジ6 画像の勾配

画像の勾配 — OpenCV-Python Tutorials 1 documentation

Laplacian, sobelx, sobely

やってみよう

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

img = cv2.imread('170519-144402.jpg', 0)
img = cv2.imread('sudoku-original.jpg', 0)
#img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

laplacian = cv2.Laplacian(img,cv2.CV_64F)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=5)

plt.subplot(2,2,1),plt.imshow(img,cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])

plt.subplot(2,2,2),plt.imshow(laplacian,cmap = 'gray')
plt.title('Laplacian'), plt.xticks([]), plt.yticks([])
cv2.imwrite('Laplacian.png', img)

plt.subplot(2,2,3),plt.imshow(sobelx,cmap = 'gray')
plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
cv2.imwrite('sobelx.png', img)

plt.subplot(2,2,4),plt.imshow(sobely,cmap = 'gray')
plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])
cv2.imwrite('sobely.png', img)

plt.show()

cv2.waitKey(0)
cv2.destroyAllWindows()

あれ?

こうなった・・・。

f:id:pongsuke:20170523110803p:plain

f:id:pongsuke:20170523110804p:plain

公式のサンプルは、 f:id:pongsuke:20170523110937j:plain

こうだ。

随分違うぞ?

調べた所、勝手に正規化しているせいらしい。

pyplot — Matplotlib 2.0.2 documentation

正規化しないように指定します。

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

img = cv2.imread('170519-144402.jpg', 0)
img = cv2.imread('sudoku-original.jpg', 0)

plt.subplot(2,2,1),plt.imshow(img, cmap='gray', vmin=0, vmax=255)
plt.title('Original'), plt.xticks([]), plt.yticks([])

laplacian = cv2.Laplacian(img,cv2.CV_64F)
plt.subplot(2,2,2),plt.imshow(laplacian, cmap='gray', vmin=0, vmax=255)
plt.title('Laplacian'), plt.xticks([]), plt.yticks([])
cv2.imwrite('Laplacian.png', img)

sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
plt.subplot(2,2,3),plt.imshow(sobelx, cmap='gray', vmin=0, vmax=255)
plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
cv2.imwrite('sobelx.png', img)

sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=5)
plt.subplot(2,2,4),plt.imshow(sobely, cmap='gray', vmin=0, vmax=255)
plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])
cv2.imwrite('sobely.png', img)

plt.show()

cv2.waitKey(0)
cv2.destroyAllWindows()

f:id:pongsuke:20170523114025p:plain

f:id:pongsuke:20170523113846p:plain

まだ、Laplacian の結果がおかしい。

なにかあるのだろうか・・・。

opencv チュートリアルチャレンジ5 モルフォロジー変換

モルフォロジー変換 — OpenCV-Python Tutorials 1 documentation

モルフォロジー変換

とりあえず、カーネルはこれでテスト。

kernel = np.ones((5,5),np.uint8)

[
[ 1, 1, 1, 1, 1 ],
[ 1, 1, 1, 1, 1 ],
[ 1, 1, 1, 1, 1 ],
[ 1, 1, 1, 1, 1 ],
[ 1, 1, 1, 1, 1 ]
]

ということだ。

[
[ 0, 0, 1, 0, 0 ],
[ 0, 0, 1, 0, 0 ],
[ 0, 0, 1, 0, 0 ],
[ 0, 0, 1, 0, 0 ],
[ 0, 0, 1, 0, 0 ]
]

とすれば、縦方向にだけ評価して適用してくれる、、、のかな。

元画像はこれ f:id:pongsuke:20170523103622p:plain

おそらく、黒い背景 が前提の処理だと思う。

収縮(Erosion)

img = cv2.erode(img,kernel,iterations = 1)

f:id:pongsuke:20170523103657p:plain

膨張(Dilation)

dilation = cv2.dilate(img,kernel,iterations = 1)

f:id:pongsuke:20170523103719p:plain

オープニング(Opening)

オープニング処理は 収縮の後に膨張 をする処理です.上述したようにノイズ除去に有効です

opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)

f:id:pongsuke:20170523103728p:plain

クロージング(Closing)

クロージング処理はオープニング処理の逆の処理を指し, 膨張の後に収縮 をする処理です.前景領域中の小さな(黒い)穴を埋めるのに役立ちます

closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

f:id:pongsuke:20170523103732p:plain

モルフォロジー勾配

膨張した画像と収縮した画像の差分をとる処理です.
結果として物体の外郭(境界線)が得られます.

gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)

f:id:pongsuke:20170523103738p:plain

トップハット変換

ブラックハット変換

この2つは、使いみちが分かっらない・・・

カーネルの変更

カーネルを変えてみた。

kernel = np.array([
[ 0, 0, 1, 0, 0],
[ 0, 0, 1, 0, 0],
[ 0, 0, 1, 0, 0],
[ 0, 0, 1, 0, 0],
[ 0, 0, 1, 0, 0]
], dtype = np.uint8)


img = imgOrg
img = cv2.erode(img,kernel,iterations = 1)
cv2.imshow('erode2', img)
cv2.imwrite('erode2.png', img)

全部1と比較してみる。

全部1 f:id:pongsuke:20170523104544p:plain

縦に1 f:id:pongsuke:20170523104547p:plain

opencv チュートリアルチャレンジ4 画像の平滑化

画像の平滑化 — OpenCV-Python Tutorials 1 documentation

元画像には、adaptiveThreshold を適用後の画像を使用しました。

平均

cv2.blur()

void blur(const Mat& src, Mat& dst, Size ksize, Point anchor=Point(-1, -1), int borderType=BORDER_DEFAULT)
正規化されたボックスフィルタを用いて画像を平滑化します.

パラメタ:   
src – 入力画像.
dst – src と同じサイズ,同じ型の出力画像.
ksize – 平滑化カーネルサイズ.
anchor – アンカー点.デフォルト値の Point(-1,-1) は,アンカーがカーネルの中心にあることを意味します.
borderType – 画像外のピクセルを外挿するために利用される境界モード.

やってみる

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

img = cv2.imread('170519-144402.jpg')

img = cv2.blur(img,(5,5))
cv2.imshow('blur', img)
cv2.imwrite('blur.png', img)

cv2.waitKey(0)
cv2.destroyAllWindows()

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

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

cv2.boxFilter()

関数 boxFilter は,カーネルを用いて画像の平滑化を行います

void boxFilter(const Mat& src, Mat& dst, int ddepth, Size ksize, Point anchor=Point(-1, -1), bool normalize=true, int borderType=BORDER_DEFAULT)
ボックスフィルタを用いて画像を平滑化します.

パラメタ:   
src – 入力画像.
dst – src と同じサイズ,同じ型の出力画像.
ddepth – 出力画像に求めるビット深度.かな?
ksize – 平滑化カーネルのサイズ.
anchor – アンカー点.デフォルト値の Point(-1,-1) は,アンカーがカーネル中心にあることを意味します.
normalize – カーネルが面積で正規化されているか否かを指定します.
borderType – 画像外のピクセルを外挿するために利用される境界モード

やってみる

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

img = cv2.imread('170519-144402.jpg')


#cv2.boxFilter(img, 0, (7,7), img, (-1,-1), False, cv2.BORDER_DEFAULT)
img = cv2.boxFilter(img, 0, (7,7))
cv2.imshow('boxFilter', img)
cv2.imwrite('boxFilter.png', img)

cv2.waitKey(0)
cv2.destroyAllWindows()

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

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

ガウシアンフィルタ

箱型フィルタがカーネル内のフィルタ係数が一様だったのに対して,ガウシアンフィルタは注目画素との距離に応じて重みを変えるガウシアンカーネルを採用します. cv2.GaussianBlur() 関数を使います.カーネルの縦幅と横幅(どちらも奇数)に加え,ガウシアンの標準偏差値sigmaX(横方向)とsigmaY(縦方向)を指定する必要があります.sigmaXしか指定されなければ,sigmaYはsigmaXと同じだとみなされます.どちらの値も0にした場合,カーネルのサイズから自動的に計算されます.ガウシアンフィルタは白色雑音の除去に適しています.

img = cv2.GaussianBlur(img,(5,5),0)

これが f:id:pongsuke:20170522173051p:plain

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

中央値フィルタ

cv2.medianBlur() 関数はカーネル内の全画素の中央値を計算します.ごま塩ノイズのようなノイズに対して効果的です.箱型フィルタとガウシアンフィルタの出力結果は原画像中には存在しない画素値を出力とするのに対して,中央値フィルタの出力は常に原画像中から選ばれています.そのためごま塩ノイズのような特異なノイズに対して効果的です.カーネルサイズは奇数でなければいけません.

img = cv2.medianBlur(img,5)

これが f:id:pongsuke:20170522173051p:plain

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

バイラテラルフィルタ

前述したように,フィルタリングは一般的にエッジまでぼかしてしまいますが, cv2.bilateralFilter() によって使えるバイラテラルフィルタはエッジを保存しながら画像をぼかすことができます.しかし,上記のフィルタリングに比べて処理速度が遅いという欠点があります.既に紹介したガウシアンフィルタは注目がその近傍領域に対して重み付け平均した値を出力します.これはガウシアンフィルタが注目画素の近傍の画素のみを考慮した関数であることを意味します.近傍領域内の画素が似たような値を持っているか否か,注目画素がエッジ上に存在するか否かなどは考慮されません.結果としてガウシアンフィルタはエッジの劣化が不可避です.

バイラテラルフィルタも同様にガウシアンフィルタを採用していますが,画素値の差を考慮した関数として別のガウシアンフィルタも同時に使用します.一つ目のガウシアンフィルタはフィルタリングに使用する画素は ‘空間的に近い位置にある’ことを保証してくれます.一方で,二つ目のガウシアンフィルタは注目画素に似た画素値を持つ画素の値のみ考慮してフィルタリングすることを保証します.結果としてバイラテラルフィルタはエッジを保存した画像のぼかしを実現できることになります.

img = cv2.bilateralFilter(img,9,75,75)

これが f:id:pongsuke:20170522173051p:plain

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

ん?ほぼ一緒?

adaptiveThreshold 適用後だと、既に処理が終わっている感じなのかな?