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()
検索対象画像
テンプレート画像
マッチ結果
なんというか、ほぼぴったり一致しないとヒットしない。
「顔」のような抽象的な検索ではない。
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()
あれ?
こうなった・・・。
公式のサンプルは、
こうだ。
随分違うぞ?
調べた所、勝手に正規化しているせいらしい。
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()
まだ、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 ] ]
とすれば、縦方向にだけ評価して適用してくれる、、、のかな。
元画像はこれ
おそらく、黒い背景
が前提の処理だと思う。
収縮(Erosion)
img = cv2.erode(img,kernel,iterations = 1)
膨張(Dilation)
dilation = cv2.dilate(img,kernel,iterations = 1)
オープニング(Opening)
オープニング処理は 収縮の後に膨張 をする処理です.上述したようにノイズ除去に有効です
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
クロージング(Closing)
クロージング処理はオープニング処理の逆の処理を指し, 膨張の後に収縮 をする処理です.前景領域中の小さな(黒い)穴を埋めるのに役立ちます
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
モルフォロジー勾配
膨張した画像と収縮した画像の差分をとる処理です.
結果として物体の外郭(境界線)が得られます.
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
トップハット変換
ブラックハット変換
この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
縦に1
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()
これが
こう
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()
これが
こう
ガウシアンフィルタ
箱型フィルタがカーネル内のフィルタ係数が一様だったのに対して,ガウシアンフィルタは注目画素との距離に応じて重みを変えるガウシアンカーネルを採用します. cv2.GaussianBlur() 関数を使います.カーネルの縦幅と横幅(どちらも奇数)に加え,ガウシアンの標準偏差値sigmaX(横方向)とsigmaY(縦方向)を指定する必要があります.sigmaXしか指定されなければ,sigmaYはsigmaXと同じだとみなされます.どちらの値も0にした場合,カーネルのサイズから自動的に計算されます.ガウシアンフィルタは白色雑音の除去に適しています.
img = cv2.GaussianBlur(img,(5,5),0)
これが
こう
中央値フィルタ
cv2.medianBlur() 関数はカーネル内の全画素の中央値を計算します.ごま塩ノイズのようなノイズに対して効果的です.箱型フィルタとガウシアンフィルタの出力結果は原画像中には存在しない画素値を出力とするのに対して,中央値フィルタの出力は常に原画像中から選ばれています.そのためごま塩ノイズのような特異なノイズに対して効果的です.カーネルサイズは奇数でなければいけません.
img = cv2.medianBlur(img,5)
これが
こう
バイラテラルフィルタ
前述したように,フィルタリングは一般的にエッジまでぼかしてしまいますが, cv2.bilateralFilter() によって使えるバイラテラルフィルタはエッジを保存しながら画像をぼかすことができます.しかし,上記のフィルタリングに比べて処理速度が遅いという欠点があります.既に紹介したガウシアンフィルタは注目がその近傍領域に対して重み付け平均した値を出力します.これはガウシアンフィルタが注目画素の近傍の画素のみを考慮した関数であることを意味します.近傍領域内の画素が似たような値を持っているか否か,注目画素がエッジ上に存在するか否かなどは考慮されません.結果としてガウシアンフィルタはエッジの劣化が不可避です.
バイラテラルフィルタも同様にガウシアンフィルタを採用していますが,画素値の差を考慮した関数として別のガウシアンフィルタも同時に使用します.一つ目のガウシアンフィルタはフィルタリングに使用する画素は ‘空間的に近い位置にある’ことを保証してくれます.一方で,二つ目のガウシアンフィルタは注目画素に似た画素値を持つ画素の値のみ考慮してフィルタリングすることを保証します.結果としてバイラテラルフィルタはエッジを保存した画像のぼかしを実現できることになります.
img = cv2.bilateralFilter(img,9,75,75)
これが
こう
ん?ほぼ一緒?
adaptiveThreshold 適用後だと、既に処理が終わっている感じなのかな?
opencv チュートリアルチャレンジ2 画像の幾何変換
画像の幾何変換 — OpenCV-Python Tutorials 1 documentation
リサイズ
void cvResize(const CvArr* src, CvArr* dst, int interpolation=CV_INTER_LINEAR) 画像をリサイズします. パラメタ: src – 入力画像 dst – 出力画像 interpolation – 補間手法: CV_INTER_NN 最近隣接補間 CV_INTER_LINEAR バイリニア補間(デフォルト) CV_INTER_AREA ピクセル領域同士の関係を利用したリサンプリング.画像縮小の際は,モアレの無い処理結果を得ることができる手法です.拡大の際は, CV_INTER_NN と同様です CV_INTER_CUBIC バイキュービック補間
やってみる
#!/usr/bin/env python # -*- coding: utf-8 -* import cv2 img = cv2.imread('170519-174830.jpg') height, width = img.shape[:2] img = cv2.resize(img,(2*width, 2*height), interpolation = cv2.INTER_CUBIC) cv2.imshow('resize INTER_CUBIC', img) img = cv2.resize(img,(2*width, 2*height), interpolation = cv2.INTER_LINEAR) cv2.imshow('resize INTER_LINEAR', img) cv2.waitKey(0) cv2.destroyAllWindows()
並進
縦横にスライドさせる
行列の書き方を、きちんと把握する事が大事そう。
Comments from the Wiki void cvWarpAffine(const CvArr* src, CvArr* dst, const CvMat* mapMatrix, int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS, CvScalar fillval=cvScalarAll(0)) 画像のアフィン変換を行います. パラメタ: src – 入力画像 dst – 出力画像 mapMatrix – 2\times 3 の変換行列 flags – 補間手法,および以下に示すオプションフラグの組み合わせ: CV_WARP_FILL_OUTLIERS 出力画像の全ピクセルを埋めます.対応するピクセルが入力画像の範囲外であるようなピクセルには,値として fillval がセットされます CV_WARP_INVERSE_MAP これは, matrix が出力画像から入力画像への逆変換であることを表します.したがって,この行列を直接ピクセル補間に利用できます.このフラグが指定されていない場合は,この関数が mapMatrix の逆変換を求めます fillval – 対応のとれない点を埋める値
やってみる
#!/usr/bin/env python # -*- coding: utf-8 -* import cv2 import numpy as np img = cv2.imread('170519-174830.jpg') height, width = img.shape[:2] M = np.float32([ [1, 0, 100], [0, 1, 50] ]) img = cv2.warpAffine(img, M, (width,height)) cv2.imshow('warpAffine', img) cv2.waitKey(0) cv2.destroyAllWindows()
これが
こう
回転
回転の基本は、変換行列
[[ cosΘ, -sinΘ]. [ sinΘ, cosΘ]]
らしいが、opencvでは、スケーリングも同時に行い,回転の中心位置を変更でき
る変換行列を使うらしい。
そしてその変換行列は、GetRotationMatrix2D で求める。
CvMat* cv2DRotationMatrix(CvPoint2D32f center, double angle, double scale, CvMat* mapMatrix) 2次元回転のアフィン変換行列を求めます. パラメタ: center – 入力画像における回転中心 angle – 度単位で表される回転角度.正の値は,反時計回りの回転を意味します(座標原点は左上にあると仮定されます) scale – 等方性スケーリング係数 mapMatrix – 2\times 3 の出力行列へのポインタ
だ。
やってみる
#!/usr/bin/env python # -*- coding: utf-8 -* import cv2 import numpy as np img = cv2.imread('170519-174830.jpg') height, width = img.shape[:2] x = 45 M = cv2.getRotationMatrix2D((width/2,height/2), x, 1) img = cv2.warpAffine(img, M, (width,height)) cv2.imshow('warpAffineRotate', img) cv2.imwrite('warpAffineRotate.png', img) cv2.waitKey(0) cv2.destroyAllWindows()
これが
こう
アフィン変換
GetAffineTransform
変換行列は GetAffineTransform
でもとめる。
CvMat* cvGetAffineTransform(const CvPoint2D32f* src, const CvPoint2D32f* dst, CvMat* mapMatrix) 3組の対応点を用いてアフィン変換行列を求めます. パラメタ: src – 入力画像における三角形の3つの頂点座標 dst – 出力画像における,入力画像の3点に対応する3つの頂点座標 mapMatrix – 2 \times 3 の出力行列へのポインタ
少なくとも3組必要
との記述があるが、4組与えるとエラーになるので、3組ですね。
#!/usr/bin/env python # -*- coding: utf-8 -* import cv2 import numpy as np img = cv2.imread('170519-174830.jpg') rows, cols, ch = img.shape pts1 = np.float32([[205,130],[204,213],[368,107]]) pts2 = np.float32([[100,100],[100,200],[400,100]]) M = cv2.getAffineTransform(pts1,pts2) img = cv2.warpAffine(img,M,(cols,rows)) cv2.imshow('warpAffineAffine', img) cv2.imwrite('warpAffineAffine.png', img) cv2.waitKey(0) cv2.destroyAllWindows()
やってみる
これが
こう
射影変換
変換行列を計算するためには少なくとも4組の対応点の座標が必要になります.これら4点の内どの3点をとっても同一直線上に載らないような4点を選ぶ必要が有ります
と、あります。
CvMat* cvGetPerspectiveTransform(const CvPoint2D32f* src, const CvPoint2D32f* dst, CvMat* mapMatrix) 3組の対応点を用いて透視変換行列を求めます. パラメタ: src – 入力画像における四角形の4つの頂点座標 dst – 出力画像における,入力画像の4点に対応する4つの頂点座標 map_matrix – 3 \times 3 の出力行列へのポインタ
やってみる
カードの角の座標は、手作業で調べました。
#!/usr/bin/env python # -*- coding: utf-8 -* import cv2 import numpy as np img = cv2.imread('170519-174830.jpg') rows, cols, ch = img.shape pts1 = np.float32([[205,130],[204,213],[373,107],[400,186]]) pts2 = np.float32([[100,100],[100,250],[400,100],[400,250]]) M = cv2.getPerspectiveTransform(pts1,pts2) img = cv2.warpPerspective(img,M,(cols,rows)) cv2.imshow('warpAffinePerspective', img) cv2.imwrite('warpAffinePerspective.png', img) cv2.waitKey(0) cv2.destroyAllWindows()
これが
こう
opencv チュートリアルチャレンジ13 ハフ変換による直線検出
ハフ変換による直線検出 — OpenCV-Python Tutorials 1 documentation
直線を検出したい
cv2.HoughLines
標準ハフ変換
void HoughLines(Mat& image, vector<Vec2f>& lines, double rho, double theta, int threshold, double srn=0, double stn=0) 標準ハフ変換を用いて,2値画像から直線を検出します. パラメタ: image – 8ビット,シングルチャンネルの2値入力画像.この画像は関数により書き換えられる可能性があります. lines – 検出された直線が出力されるベクトル.各直線は,2要素のベクトル (\rho, \theta) で表現されます. \rho は原点(画像の左上コーナー)からの距離, \theta はラジアン単位で表される直線の回転角度(0 \sim 垂直線, \pi/2 \sim 水平線)です. rho – ピクセル単位で表される投票空間の距離分解能. theta – ラジアン単位で表される投票空間の角度分解能. threshold – 投票の閾値パラメータ.十分な票( >\texttt{threshold} )を得た直線のみが出力されます. srn – マルチスケールハフ変換において,距離分解能 rho の除数となる値.投票空間の粗い距離分解能は rho となり,細かい分解能は rho/srn となります.もし srn=0 かつ stn=0 の場合は,古典的ハフ変換が利用されます.そうでない場合は,両方のパラメータが正値である必要があります. stn – マルチスケールハフ変換において,角度分解能 theta の除数となる値.
やってみる
lines = cv2.HoughLines(edges, 1, np.pi/180, 200) for line in lines: rho, theta = line[0] a = np.cos(theta) b = np.sin(theta) x0 = a*rho y0 = b*rho x1 = int(x0 + 1000*(-b)) y1 = int(y0 + 1000*(a)) x2 = int(x0 - 1000*(-b)) y2 = int(y0 - 1000*(a)) cv2.line(img,(x1,y1),(x2,y2),(0,0,255),1)
これが
こう
cv2.HoughLinesP
確率的ハフ変換
void HoughLinesP(Mat& image, vector<Vec4i>& lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0) 確率的ハフ変換を利用して,2値画像から線分を検出します. パラメタ: image – 8ビット,シングルチャンネルの2値入力画像.この画像は関数により書き換えられる可能性があります. lines – 検出された線分が出力されるベクトル.各線分は,4要素のベクトル (x_1, y_1, x_2, y_2) で表現されます.ここで (x_1,y_1) および (x_2, y_2) は,検出された各線分の端点です. rho – ピクセル単位で表される投票空間の距離分解能. theta – ラジアン単位で表される投票空間の角度分解能. threshold – 投票の閾値パラメータ.十分な票( >\texttt{threshold} )を得た直線のみが出力されます. minLineLength – 最小の線分長.これより短い線分は棄却されます. maxLineGap – 2点が同一線分上にあると見なす場合に許容される最大距離.
やってみる
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 200, 100, 10) for line in lines: x1,y1,x2,y2 = line[0] cv2.line(img,(x1,y1),(x2,y2),(0,255,0),2)
これが
こう
opencv チュートリアルチャレンジ3 画像のしきい値処理
画像のしきい値処理 — OpenCV-Python Tutorials 1 documentation
元画像
cv2.threshold
この関数は,シングルチャンネルの配列に対して,ある定数での閾値処理を行います.これは,グレースケールからの2値画像生成(関数 compare も,この目的に利用できます)やノイズ除去(つまり,小さすぎたり大きすぎたりする値をはじく処理)などに利用される場合が多いです.この関数がサポートする閾値処理にはいくつかの種類があり,それは引数 thresholdType によって決定されます:
double threshold(const Mat& src, Mat& dst, double thresh, double maxVal, int thresholdType) 配列の要素に対して,ある定数での閾値処理を行います. パラメタ: src – 入力配列(シングルチャンネル,8ビット,あるいは32ビット浮動小数点型). dst – src と同じサイズ,同じ型の出力配列. thresh – 閾値. maxVal – 閾値処理の種類が THRESH_BINARY や THRESH_BINARY_INV の場合に利用される,最大値の値. thresholdType – 閾値処理の種類(以下の説明を参照してください).
やってみる
ret, img = cv2.threshold(img, 60, 255, cv2.THRESH_BINARY)
ret, img = cv2.threshold(img, 150, 255, cv2.THRESH_BINARY)
cv2.adaptiveThreshold
適応的閾値処理
void adaptiveThreshold(const Mat& src, Mat& dst, double maxValue, int adaptiveMethod, int thresholdType, int blockSize, double C) 配列に対して,適応的な閾値処理を行います. パラメタ: src – 8ビット,シングルチャンネルの入力画像. dst – src と同じサイズ,同じ型の出力画像. maxValue – 条件を満足するピクセルに割り当てられる非0の値.説明を参照してください. adaptiveMethod – 利用される適応的閾値アルゴリズム: ADAPTIVE_THRESH_MEAN_C または ADAPTIVE_THRESH_GAUSSIAN_C (説明を参照してください). thresholdType – 閾値の種類. THRESH_BINARY または THRESH_BINARY_INV のどちらか. blockSize – ピクセルの閾値を求めるために利用される近傍領域のサイズ.3, 5, 7, など. C – 平均または加重平均から引かれる定数(説明を参照).通常,これは正値ですが,0や負値の可能性もあります.
やってみる
img = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 15, 20)