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 適用後だと、既に処理が終わっている感じなのかな?

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()

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

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

回転

回転の基本は、変換行列

[[ 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()

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

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

アフィン変換

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()

やってみる

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

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

射影変換

変換行列を計算するためには少なくとも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()

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

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

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)

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

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

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)

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

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

opencv チュートリアルチャレンジ3 画像のしきい値処理

画像のしきい値処理 — OpenCV-Python Tutorials 1 documentation

元画像

f:id:pongsuke:20170519145649j:plain

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 – 閾値処理の種類(以下の説明を参照してください).

f:id:pongsuke:20170519161608p:plain

やってみる

ret, img = cv2.threshold(img, 60, 255, cv2.THRESH_BINARY)

f:id:pongsuke:20170519161854p:plain

ret, img = cv2.threshold(img, 150, 255, cv2.THRESH_BINARY)

f:id:pongsuke:20170519161902p:plain

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)

f:id:pongsuke:20170519162151p:plain

opencv チュートリアルチャレンジ9 OpenCVにおける輪郭(領域)

輪郭に関するチュートリアル

OpenCVにおける輪郭(領域) — OpenCV-Python Tutorials 1 documentation

輪郭抽出

OpenCVの輪郭検出は,黒い背景から白い物体の輪郭を検出すると仮定しています.物体は白(明るい色),背景は黒(暗い色)と覚えておいてください.

とある。

cv2.findContours

輪郭抽出と、その輪郭に対する処理を人多りやてみる。

void findContours(const Mat& image, vector<vector<Point> >& contours, int mode, int method, Point offset=Point())
2値画像中の輪郭を検出します.

パラメタ:   
image – 入力画像,8ビット,シングルチャンネル.0以外のピクセルは 1として,0のピクセルは0のまま扱われます.つまり,入力画像は 2値画像 として扱われます.グレースケールやカラー画像から2値画像を得るには, compare() , inRange() , threshold() , adaptiveThreshold() , Canny() などの関数を利用します.また,この関数は,輪郭抽出処理中に入力画像 image の中身を書き換えます.
contours – 検出された輪郭.各輪郭は,点のベクトルとして格納されます.
hiararchy – オプション.画像のトポロジーに関する情報を含む出力ベクトル.これは,輪郭数と同じ数の要素を持ちます.各輪郭 contours[i] に対して,要素 hierarchy[i][0] , hiearchy[i][1] , hiearchy[i][2] , hiearchy[i][3] にはそれぞれ,同じ階層レベルに存在する前後の輪郭,最初の子輪郭,および親輪郭の contours インデックス(0 基準)がセットされます.また,輪郭 i において,前後,親,子の輪郭が存在しない場合,それに対応する hierarchy[i] の要素は,負の値になります.
mode –
輪郭抽出モード
CV_RETR_EXTERNAL 最も外側の輪郭のみを抽出します.すべての輪郭に対して hierarchy[i][2]=hierarchy[i][3]=-1 がセットされます.
CV_RETR_LIST すべての輪郭を抽出しますが,一切の階層構造を保持しません.
CV_RETR_CCOMP すべての輪郭を抽出し,それらを2階層構造として保存します:上のレベルには,連結成分の外側の境界線が,下のレベルには,連結成分の内側に存在する穴の境界線が属します.ある連結成分の穴の内側に別の輪郭が存在する場合,その穴は上のレベルに属します.
CV_RETR_TREE すべての輪郭を抽出し,入れ子構造になった輪郭を完全に表現する階層構造を構成します.この完全な階層構造は,OpenCVの contours.c デモで見ることができます.
method –
輪郭の近似手法:
CV_CHAIN_APPROX_NONE すべての輪郭点を完全に格納します.つまり,この手法により格納された任意の隣り合う2点は,互いに8近傍に存在します.
CV_CHAIN_APPROX_SIMPLE 水平・垂直・斜めの線分を圧縮し,それらの端点のみを残します.例えば,まっすぐな矩形の輪郭線は,4つの点にエンコードされます.
CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS Teh-Chinチェーン近似アルゴリズムの1つを適用します. TehChin89 を参照してください.
offset – オプションのオフセット.各輪郭点はこの値の分だけシフトします.これは,ROIの中で抽出された輪郭を,画像全体に対して位置づけて解析する場合に役立ちます.

通常、グレースケール化して、輪郭を抽出して、見やすいように色を載せると思う。

面倒なので、カラー化したものを変換して乗せてしまう。

元の画像は、これ。 f:id:pongsuke:20170519181729j:plain

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

img = cv2.imread('170519-174830.jpg')
img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

img = cv2.GaussianBlur(img, (11, 11), 0)
#cv2.imshow('GaussianBlur', img)

#ret, img = cv2.threshold(img, 60, 255, cv2.THRESH_BINARY)
#cv2.imshow('threshold1', img)
#cv2.imwrite('threshold1.png', img)

#ret, img = cv2.threshold(img, 150, 255, cv2.THRESH_BINARY)
#cv2.imshow('threshold2', img)
#cv2.imwrite('threshold2.png', img)

img = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 15, 5)
cv2.imshow('adaptiveThreshold', img)
#cv2.imwrite('adaptiveThreshold.png', img)

#img, contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
img, contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print("{} contours.".format(len(contours)))

# カラー化
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)

#img = cv2.drawContours(img, contours, -1, (0,0,255), 1)
#cv2.imshow('drawContours', img)
#cv2.imwrite('drawContours.png', img)

if len(contours) > 0:
    for i, contour in enumerate(contours):
        #print("{}, {}".format(i, len(contour)))

        # area:面積
        area = cv2.contourArea(contour)
        if area < 100:
            continue

        # moments:重心
        M = cv2.moments(contour)
        if M["m00"] != 0:
            cx = int(M["m10"] / M["m00"])
            cy = int(M["m01"] / M["m00"])
        else:
            cx, cy = 0, 0
        # 重心を中心にした円
        cv2.circle(img, (cx, cy), int(math.sqrt(area)), (0,255,0), 1, 8)

        # 最小外接円
        (x, y), radius = cv2.minEnclosingCircle(contour)
        center = (int(x),int(y))
        radius = int(radius)
        img = cv2.circle(img,center,radius,(255,255,0),2)

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

        # 凸包
        hull = cv2.convexHull(contour)
        img = cv2.drawContours(img,[hull],0,(255,255,0),2)

        # 外接矩形
        rect = cv2.boundingRect(contour)
        cv2.rectangle(img, (rect[0], rect[1]), (rect[0]+rect[2], rect[1]+rect[3]), (0,0,255), 1)

        # 回転を考慮した外接矩形
        rect = cv2.minAreaRect(contour)
        box = cv2.boxPoints(rect)
        box = np.int0(box)
        img = cv2.drawContours(img,[box],0,(0,255,255),1)

        # 楕円のフィッティング
        ellipse = cv2.fitEllipse(contour)
        img = cv2.ellipse(img,ellipse,(0,255,0),2)

        #直線のフィッティング
        rows,cols = img.shape[:2]
        [vx,vy,x,y] = cv2.fitLine(contour, cv2.DIST_L2,0,0.01,0.01)
        lefty = int((-x*vy/vx) + y)
        righty = int(((cols-x)*vy/vx)+y)
        img = cv2.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)

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

cv2.waitKey(0)
cv2.destroyAllWindows()

こうなった f:id:pongsuke:20170519182204p:plain

キーワード

  1. アスペクト比

  2. Extent

  3. Solidity

  4. 等価直径

  5. 傾き

  6. マスクと画素点

  7. 最大値,最小値とその位置

  8. 平均色と平均値

  9. 端点

凸性の欠陥

こんどやる

輪郭の階層情報

いつかやる