1. 概要数年間に渡って画像処理の仕事をしてきた時に残っていた古典的画像処理周りのメモ 特に、2012年以前のCNNを代表とするDNN系の手法、最近のViT系の手法は別の記事にまとめる 画像処理でよく使うlibraryはnumpy, opencv, scikit-image, matplotlib, jupyterlab, pytorch, dlib, imageprayなどなど 個人的に使うOpenCVのスニペットも残す 2. 知識メモ2.1. 基本2.1.1. 画像の種類大きく分けて2種類ある。
ベクター画像ベクター画像は、線や曲線といった図形集合の重ね合わせをデータとして保持した形式 e.g. svg ラスター画像ラスター画像はピクセル(画素ともいう)と呼ばれる色の数値を格子状に並べたデータを保持した形式 e.g. png, jpg 2.1.2. 画像の圧縮方式の違いPNGは可逆圧縮だが、JPGは非可逆圧縮 つまり、JPGはデータの圧縮の際にデータを削除するので小さな線分やオブジェクトが劣化する可能性がある 一般的には、PNGを使うのを推奨 ただし、ベクトル化した場合はSVGを推奨 2.1.3. 色空間色空間(カラースペース)とは、色を数値に変換するシステムのこと
カラースペース レンダリングするメディアタイプによって、色域は異なるので注意。
レンダリングによる差 OpenCVでは次の型だが、おおむねCV_8U=np.uint8を使う。
型 範囲 CV_8U 0 - 255 CV_16U 0 - 65535 CV_32F 0 - 1
また、色変換によって分類の一助になる可能性もあるため、分析をする前に先に確認するのも良い。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import cv2 as cv
import matplotlib.pyplot as plt
# グリッド形式で色空間のチャンネルを表示する関数
def show_color_space_grid ( image_path , color_spaces , grid_size = ( 3 , 3 ), output_filename = "color_spaces.png" ):
image = cv . imread ( image_path )
# グリッドを作成
fig , axs = plt . subplots ( grid_size [ 0 ], grid_size [ 1 ], figsize = ( 40 , 30 ))
# 各色空間の変換と表示
index = 0 # グリッドのインデックス
for color_space in color_spaces :
conversion_code = color_space [ "code" ]
channels = color_space [ "channels" ]
title = color_space [ "title" ]
# 指定した色空間に変換
converted_image = cv . cvtColor ( image , conversion_code )
# 各チャンネルを軸に表示
for j , channel_name in enumerate ( channels ):
row , col = divmod ( index , grid_size [ 1 ]) # 行と列を計算
channel_image = converted_image [:, :, j ]
# カラーマップを設定
cmap = 'gray' # if len(channels) == 1 else None
# 軸にプロット
axs [ row ][ col ] . imshow ( channel_image , cmap = cmap )
axs [ row ][ col ] . set_title ( f "{title} - {channel_name}" )
axs [ row ][ col ] . axis ( "off" )
index += 1
# ファイルに保存
plt . tight_layout ()
plt . savefig ( output_filename )
plt . close ()
# 使用する色空間
color_spaces = [
{ "code" : cv . COLOR_BGR2BGRA , "channels" : [ "B" , "G" , "R" ], "title" : "BGR" },
{ "code" : cv . COLOR_BGR2HSV , "channels" : [ "H" , "S" , "V" ], "title" : "HSV" },
{ "code" : cv . COLOR_BGR2Lab , "channels" : [ "L" , "a" , "b" ], "title" : "Lab" },
{ "code" : cv . COLOR_BGR2Luv , "channels" : [ "L" , "u" , "v" ], "title" : "Luv" },
{ "code" : cv . COLOR_BGR2HLS , "channels" : [ "H" , "L" , "S" ], "title" : "HLS" },
{ "code" : cv . COLOR_BGR2YUV , "channels" : [ "Y" , "U" , "V" ], "title" : "YUV" },
{ "code" : cv . COLOR_BGR2XYZ , "channels" : [ "X" , "Y" , "Z" ], "title" : "XYZ" },
{ "code" : cv . COLOR_BGR2YCrCb , "channels" : [ "Y" , "Cr" , "Cb" ], "title" : "YCrCb" },
]
image_path = "test.png"
show_color_space_grid ( image_path , color_spaces , grid_size = ( 8 , 3 ))
Copy 入力画像
名刺画像 出力画像
変換後の白黒画像 カラースペースの特徴は以下になる。
カラースペース 変換コード チャネル 特徴 BGR cv.COLOR_BGR2BGRA B, G, R 青、緑、赤の基本的なRGBカラースペース。一般的なカメラで使用される形式。 BGRA cv.COLOR_BGR2BGRA B, G, R, A BGRカラースペースにアルファチャネルを追加。透過性を持つ。 HSV cv.COLOR_BGR2HSV H, S, V 色相、彩度、明度のカラースペース。色を直感的に理解しやすい。 Lab cv.COLOR_BGR2Lab L, a, b 光度(L)と2つの色成分(a、b)のカラースペース。人間の視覚に近い。 Luv cv.COLOR_BGR2Luv L, u, v 光度(L)と2つの色成分(u、v)のカラースペース。CIE 1976 L*u*v* 標準。 HLS cv.COLOR_BGR2HLS H, L, S 色相、光度、彩度のカラースペース。HSVと似ているが、光度が異なる。 YUV cv.COLOR_BGR2YUV Y, U, V 輝度(Y)と2つの色成分(U、V)のカラースペース。ビデオ圧縮に使用される。 XYZ cv.COLOR_BGR2XYZ X, Y, Z CIE 1931標準色度図のカラースペース。標準色度としてよく使用される。 YCrCb cv.COLOR_BGR2YCrCb Y, Cr, Cb 輝度(Y)と2つの色成分(Cr、Cb)のカラースペース。映像処理に使用される。
2.1.4. 画像の状態画像が現実の写像になっていない時に使われる用語。
英語 日本語 対義語 noised ノイズのある clean rotated 回転した upright distorted 歪んだ(レンズの歪みなどの、不規則な変形) undistorted skewd 傾斜した(透視的効果などによる特定の方向への変形) Aligned(整列した)など unclear アンクリア clear blurred ぼやけた sharp superimposed スーパーインポーズされた(重ねられた) isolated
他にも色々あるが、imgaugのtoolのgifが分かりやすい。
aleju/imgaug: Image augmentation for machine learning experiments.
2.2. 座標系2.2.1. 代表的な座標系いくつかあるが、次などが頻出する
座標系 2.2.2. 絶対座標・相対座標2次元の画像処理では、ピクセル座標が絶対座標で、ROI内の座標が相対座標。
ROI座標 2.3. 紙に対する処理2.3.1. アンチエイリアス「アンチエイリアス」は描いた線のフチを僅かにボカして、なめらかな線に見えるよう変換するといった、ペン・図形・選択範囲などあらゆる描画ツールに付加できる機能。
アンチエイリアス 2.3.2. deskewスキャンしたデータの傾きなどを修正する事。
deskew 2.3.3. 紙からデータを抜き出す鉄板パターン特に紙の場合は色が単一でデータがスパースなので、画像処理がやりやすい特徴がある。
グレースケール 白黒をインバート コンター(contour)をとる 図形化する ビット演算子で画像を合算処理する方法。
よく使われるのが、白黒でのAND ある2つの処理が合った時に、完全に両方のオペランドで黒(0)じゃないときは、白にするというやり方 つまり、2つの処理の両方で通った時のみ、表示するというやり方 1
2
3
4
5
6
>>> 1 | 1
1
>>> 0 & 1
0
>>> 0 & 0
0
Copy 2.4. カメラ2.4.1. キャリブレーションカメラが物理的にどう見ているかを理解するために、カメラの内部および外部のパラメータを特定するプロセス カメラのレンズの歪みを表現するパラメータや、カメラのワールド座標系での位置姿勢を推定できる 2.4.2. 内部パラメータ・外部パラメータ内部パラメータ 外部パラメータカメラの位置、向き、回転など、カメラが世界に対してどう配置されているかのパラメータ 2.5. ROI系2.5.1. ROIとはROI: Region of Interest Subpixelとも言ったりもする
ROI 2.5.2. Saliency Map(顕著性マップ)Saliency Mapとは、ある画像を人が見ると、視線はどこに向くか?をピクセル単位で考えたもの。
Saliency map 図:Saliency Mapの例。
左上:画像 右上:実測した視点を赤いXで示したもの 左下:Saliency Map 右下:Saliency Mapをカラーにして画像に重ねたもの 2.6. Segmentation2.6.1. セグメンテーションとはImage segmentation is the process of partitioning a digital image into multiple segments by grouping together pixel regions with some predefined characteristics. Each of the pixels in a region is similar with respect to some property, such as color, intensity, location, or texture. It is one of the most important image processing tools because it helps us to extract the objects from the image for further analysis. Moreover, the accuracy of the segmentation step determines success and failure in further image analysis.
2.6.2. セグメンテーションの4つの特徴重要なの4つの特徴
color: 色 intensity: 強度(いわゆるコントラスト) location: 場所 texture: パターン 2.6.3. セグメンテーションの種類やりかたは、discontinuity か similarityの強さに応じて2つに大分される。
The discontinuity approach splits an image according to sudden changes in intensities (edges). The similarity approach splits an image based on similar regions according to predefined criteria テクニックは次のような感じ。
segmentationテクニック 2.6.4. Watershed(分水界)とはOpenCVでのWatershed algorithmについて。
イメージはダム。ダムの水を堤防の限界まで埋めるように処理が走る。
watershed 2.7. ノイズ除去系2.7.1. デノイズcv2.fastNlMeansDenoising
を使う
デノイズ 2.7.2. Blurryな画像をClearにする方法目的に応じて使い分ける必要がある。
binarizationで分ける blurでノイズを消すGaussianBlurなどをかけてnoiseを消す 傾きが強いKernelを適用する (Sharpen Filter)1
2
sharpen_filter = np . array ([[ 0 , - 1 , 0 ], [ - 1 , 5 , - 1 ], [ 0 , - 1 , 0 ]])
sharped_img = cv2 . filter2D ( image , - 1 , sharpen_filter )
Copy Unsharp masking画像の微細構造(高周波成分)を強調するために使われる画像処理の技法。原画像をピンぼけ状態にぼかした画像(アンシャープマスク)を原画像から差し引くことで、微細構造が強調される。写真の場合は、アンシャープマスクを原画像とネガポジ反転させて重ねて焼き付ける。マスクのピンぼけ状態の度合いを変えることで、強調される微細構造の細かさを変化させることができる。
scikit image のapiをつかう モルフォロジー変換を使う ガウシアンフィルタ
Unsharp 2.8. カーネル系2.8.1. ConvとPoolの違いDNNではおおむね次の2つのフィルターをかけるが、数学的には次の違いになる。
Conv:特徴マッチの為の処理 invariant function Pool:位置情報の為の処理 variant function 2.8.2. 代表的なカーネル代表的なのは下。
代表的なカーネル Mexican hat or Laplacian filter.エッジ検出や特徴抽出、画像の特定部分を強調するために使われる
Mexican hat filter バイラテラルフィルタ(Bilateral filter)単純に注目画素の周りにある画素値を平均するのではなく、注目画素の近くにあるものをより重視して反映させようというのが重み付き平均化 じゃあその重みの振り分けをどうするかというときに、正規分布に従って振ればいいんじゃないというのがガウシアンフィルタ
Bilateral Filter ノンローカルミーンフィルタ(Non-local Means Filter)バイラテラルフィルタは注目画素の画素値と周辺画素の画素値の差に応じた重みをつけたけど、ノンローカルミーンフィルタはテンプレートマッチングのように周辺画素を含めた領域が、注目画素の周辺領域とどれくらい似通っているかによって重みを決定するフィルター
Bilateral Filter ガボールフィルタ(Gabor filter)ガボール関数はガウス関数と正弦・余弦関数から構成されている関数 任意の周波数成分を抽出するフィルタリング機能を持つ
ガボールフィルタ CNNのフィルタCNNのConv、Poolなどのカーネルを古典的なアルゴと併用してを使う事もできる ImageNetなどで学習済みのCNNモデルのDNNを使ってベクトルを産出しできる それをPCAなどにかける事で、少数画像で教師あり学習(クラスタリング)をする事も可能 2.8.3. 白と黒の意味CVだと、白=1、黒=0となる 0は谷/海となり、1は峰/丘となる 処理でも残したい所=白、消したい所=黒とする グレースケールのときは、黒=0、白=255 0は何かけても0なので、掛け算の処理をしても無効化もできる さらに、ビット変換の際にも役立つ 白黒反転したいときは、インバートする 白い所(180 ~ 255)を消す例。
1
2
3
black_filter = cv2 . inRange ( sharped_denoised_img_roi , 0 , 180 ) # 0 ~ 180 => white
img_roi_bitwized_grey = black_filter & sharped_denoised_img_roi
plt . imshow ( img_roi_bitwized_grey , cmap = "gray" )
Copy 2.9. ベクトル化2.9.1. ベクトル化(スケルトン化)次のように情報を縮尺する事。
ベクトル化 2.9.2. グラフ化この場合は、赤い点がnode そして、緑の線がedgeとなる edgeは2つのnode(start, end)をもつ これらの2つの点を結ぶedgeをベクトル化(曲線化)する方法がベジェ曲線
Graph 2.9.3. ベジェ曲線ベジェ曲線(Bezier Curve)とは、複雑な凹凸を持つ曲線でも、制御点の座標と関数により指定した二点間を結ぶ滑らかな曲線を表現できる手法。
2.10. 凸包(convex hull)2.10.1. 凸包とは凸包(convex hull)とは, 与えられた点をすべて包含する最小の凸多角形(凸多面体)のこと。
Convex hull 2.10.2. 凸性の欠陥とは?簡単に言うと、凸性の欠陥とは、手のひらのくぼみと赤線の凸包の間のこと。
凸性の欠陥 2.10.3. 凸製の欠陥の例 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import cv2
import numpy as np
#img = cv2.imread('sqimage.png')
img = cv2 . imread ( 'tstar2.png' )
gray = cv2 . cvtColor ( img , cv2 . COLOR_BGR2GRAY )
_ , thresh = cv2 . threshold ( gray , 0 , 255 , cv2 . THRESH_BINARY | cv2 . THRESH_OTSU )
bw = cv2 . bitwise_not ( thresh )
contours , hierarchy = cv2 . findContours ( bw , 2 , 1 )
res_img = img // 2 + 128
# 輪郭描画
for cnt in contours :
cv2 . drawContours ( res_img ,[ cnt ], 0 ,( 255 , 128 , 0 ), 2 )
for cnt in contours :
# 凸性の確認
print ( 'isContourConvex' , cv2 . isContourConvex ( cnt ))
# 凸包(Convex Hull)
# returnPoints=Trueを指定するとconvex hull上の点の座標
# False を指定するとconvex hull上の点に対応する
# 輪郭上の点のインデックスを返す.
hull = cv2 . convexHull ( cnt , returnPoints = False )
# 凸性欠陥情報の取得
defects = cv2 . convexityDefects ( cnt , hull )
if defects is None :
print ( 'defects is None!' )
continue
for de in defects :
# [ 始点, 終点, 最も遠い点, 最も遠い点までの近似距離]
s_idx , e_idx , f_idx , d = de [ 0 ,:]
start = ( * cnt [ s_idx ][ 0 ],) # 始点
end = ( * cnt [ e_idx ][ 0 ],) # 終点
far = ( * cnt [ f_idx ][ 0 ],) # 最も遠い点
cv2 . line ( res_img , start , end ,( 0 , 255 , 0 ), 2 ) # 緑の線分
cv2 . circle ( res_img , far , 5 ,[ 0 , 0 , 255 ], - 1 ) # 赤の点
cv2 . imshow ( 'convexityDefects test' , res_img )
cv2 . waitKey ( 0 )
cv2 . destroyAllWindows ()
Copy
入力と出力 2.11. 検出2.11.1. 線分検出ハフ変換
直線や円などの幾何学的形状を画像から検出するための手法 極座標系で点Pを表現し、そのハフ空間の中から選出する手法 Canny法でエッジを抽出した後処理することが多い V
ハフ変換による直線抽出パイプライン
累積空間・パラーメータ空間・ハフ空間 2.11.2. エッジ検出canny法がよく使われる 仕組みはシンプルで次のようになるガウシアンフィルターでデノイズ Sobelフィルターを使って縦横のエッジを取得 その強度(Edge Gradient / Manunitude)とOrientation(arctangentで)を求める その後、ヒステリシスによって連続性があり強度が出たものを線分と判断する
Sobel
エッジの強調
Gradients of 2d Images
ヒステリシス閾値処理 DoG(Difference of Gaussian)ガウスフィルタを使って画像を2回平滑化し、その結果を減算する方法 1つは標準偏差が小さく、もう1つは標準偏差が大きいものを用いて、差で顕著化したフィルターを作って処理する ガウスフィルタによる平滑化を経ているため、ノイズに強い特性がある LoG(Laplacian of Gaussian)ガウスフィルタとラプラシアンフィルタを用いていEdgeを強調する手法 ラプラシアンフィルターは二階微分を計算する為のフィルタ ただし、ラプラシアンフィルタはノイズに弱いので、ガウスフィルタをかける
DoG
ラプラシアンフィルター
DoGのイメージ 2.12. 画像位置合わせ2.12.1. 画像位置合わせ(image registration)画像位置合わせとは、2枚の画像の位置ずれを補正する処理のこと 画像位置合わせは、同じシーンの複数の画像を比較する時などに用いる 例えば、衛星画像解析やオプティカルフロー、医用画像の分野でよく登場する
画像位置合わせ 2.12.2. 画像位置合わせの例テンプレートの特徴を取って、キーポイントとして、マッチングしてワープする処理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import sys
import cv2 as cv
import numpy as np
template_path = sys . argv [ 1 ]
target_path = sys . argv [ 2 ]
out_path = sys . argv [ 3 ] if len ( sys . argv ) > 3 else "data/out/"
template_img = cv . imread ( template_path , cv . IMREAD_GRAYSCALE )
target_img = cv . imread ( target_path , cv . IMREAD_GRAYSCALE )
akaze = cv . AKAZE_create ()
float_kp , float_des = akaze . detectAndCompute ( target_img , None )
ref_kp , ref_des = akaze . detectAndCompute ( template_img , None )
bf = cv . BFMatcher ()
matches = bf . knnMatch ( float_des , ref_des , k = 2 )
good_matches = []
for m , n in matches :
if m . distance < 0.75 * n . distance :
good_matches . append ([ m ])
# 適切なキーポイントを選択
ref_matched_kpts = np . float32 ([ float_kp [ m [ 0 ] . queryIdx ] . pt for m in good_matches ]) . reshape ( - 1 , 1 , 2 )
sensed_matched_kpts = np . float32 ([ ref_kp [ m [ 0 ] . trainIdx ] . pt for m in good_matches ]) . reshape ( - 1 , 1 , 2 )
# ホモグラフィを計算
H , status = cv . findHomography ( ref_matched_kpts , sensed_matched_kpts , cv . RANSAC , 5.0 )
# 画像を変換
warped_image = cv . warpPerspective ( target_img , H , ( target_img . shape [ 1 ], target_img . shape [ 0 ]))
def write_grid ( img ):
y_step = 50
x_step = 50
img_y , img_x = img . shape [: 2 ]
# 横線を引く:y_stepからimg_yの手前までy_stepおきに白い(BGRすべて255)横線を引く
img [ y_step : img_y : y_step , :] = 0
# 縦線を引く:x_stepからimg_xの手前までx_stepおきに白い(BGRすべて255)縦線を引く
img [:, x_step : img_x : x_step ] = 0
return img
warped_image = write_grid ( warped_image )
template_img = write_grid ( template_img )
cv . imwrite ( f "{out_path}/template.png" , template_img )
cv . imwrite ( f "{out_path}/warped.png" , warped_image )
Copy
2.13. 特徴量マッチング2.13.1. 特徴量の出し方2.13.2. 特徴量マッチングとは?2つのオブジェクトで特徴量を抽出し、マッチングをする事 一番有名なのはテンプレートマッチング 一般的に、下のようなパイプラインになる。
2つの画像を用意する 特徴量detectorの処理まず、特徴点を出す その特徴点の特徴量を出すSIFTの場合は128次元 特徴量のmatcherの処理2つの特徴量を変換するKDTree (木構造から検索) ブルーフォース (全部を検索) 検索する手法KNN法 (K個取得する) マッチのThresholding化2つの特徴量が1以下など 2.13.3. 特徴量2.13.3.1. 大域特徴量(global feature)とは大域特徴量とは,画像全体から抽出される特徴量 カラーヒストグラムなど 2.13.3.2. 局所特徴量(local feature)とは?
2.13.4. マッチング手法2.13.4.1. FLANN based MatcherFLANN: Fast Library for Approximate Nearest Neighborsの意味特徴は名前の通り、approximateのNNを取り、BFMatcherより早く動く フローKd treeの構造を先にIndexとして作っておく。 そこからKNNかFixed-Round KNNで木構造から検索する。(木の探索)KNNの場合は、近くのK個 Fixed-Round KNNの場合は半径内にある全部 とれたそれらのdiscriptorの距離を計算してあるパラーメター内の近さならそれをマッチした特徴としている BFMatcher全部検索する なので、サブピクセルの特徴量が100で、基の特徴量が100とかの場合は、1万回もチェックする必要がある 2.13.4.1.1. FLANNの実装例radius near neighborsとは?
KNNの亜種。予め半径を決めておき、その範囲内をnear neibhbourにする K-nearestのestにならない理由は、何個かわからないから
Redius near neighbors K-nearest neighbor searchとは?
K個近くのデータを撮ってくる。それを基にデータを決める つまり、類は友を呼ぶ KDTreeアルゴリズムとは?
決定木で作る 決定木の深さ=領域の個数=n^2 バイナリツリーみたいな感じ KNNで近傍を探すのに、木構造を利用して探す see Kd-treeと最近傍探索 適応システム特論・担当三上
KNNとFixed-Radius KNNの違いとは?
KNN vs. Fixed-Radius KNN see https://ai-master.gitbooks.io/knn/content/simple-extensions.html
2.13.4.2. ブルーフォース法Brute-Force matcher is simple. It takes the descriptor of one feature in first set and is matched with all other features in second set using some distance calculation. And the closest one is returned.
ブルートフォースマッチャーはシンプルで、最初のセットの1つの特徴の記述子を取り、距離計算を使用して2番目のセットの他のすべての特徴と照合する。そして、最も近いものが返される。これを繰り返す
ブルートフォースマッチャー 2.13.5. 特徴量の比較
画像特徴量の変遷
局所特徴量 2.13.6. 特徴量の出し方2.13.6.1. Feature DetectorとFeature descriptorの違いは?次のようにまとめられる。
アルゴ input outputs 特徴 example feature detector image locations (場所) 特徴については教えてくれない corner detector feature descriptor image feature descriptors / feature vectors 特徴について教えてくれる。scale invariantなことが多い。 SHIFT, HOG, SURF, KAZE, AKAZE
2.13.6.2. HOG特徴量とは?勾配ベースの特徴量 白黒はっきりしていると取りやすい SVMを使ったりする 2.13.6.3. Haar like特徴量隣接する2つの矩形の明るさの違い OpenCVだと顔認識に使われている 2.13.6.4. FAST algorithmとは?2.13.6.5. binary string basedBRIEF アルゴリズムとは?BRIEF (Binary Robust Independent Elementary Features, 二値頑健独立基本特徴) BRISKアルゴリズムBinary Robust Invariant Scalable Keypoints (BRISK) アルゴリズム BRIEFを発展させた以下のような方法で、スケール不変性と回転不変性を得ているのがBRISKである ORB Detector(Oriented FAST and rotated BRIEF)OrientedFASTおよびrotatedBRIEFは、Ethan Rubleeetalによって最初に発表された高速で堅牢なローカル機能検出器
BRISKアルゴ 2.14. 変換2.14.1. 距離変換距離変換 (distance transform) とは、2値画像を入力としたとき、各画素の最も近い画素値0までの距離を計算した 距離マップ (distance map) を作成する処理。dst = cv2.distanceTransform(src, distanceType, maskSize[, dst[, dstType]])
で行う。
距離返還 2.14.2. 二値化2.14.2.1. Otsu法ある画像を2値化(ピクセル値を255(最大値)or 0(最小値)にする = バイナリー化 = ざっくり言うなら白黒化)にする場合、その閾値を決定する必要がある Otsu法は画像の輝度ヒストグラムを元に、いい感じの閾値を自動で決定してくれるアルゴリズム ただし、ヒストグラムが大きく二つに別れるところをバッツリと二つに分ける(=閾値を決定する)アルゴリズムなので、そういった双峰性を持たないヒストグラムに対してはうまく閾値を決定できないらしい
大津の二値化 2.14.2.2. pタイル法Pタイル法(Percentile Method)は、2値化したい領域が全データの領域に占める割合をパーセント(%)で指定し2値化する手法。
2.14.2.3. Multilevel ThresholdingOtsuの二値化と違い、複数のThreasholdでクラスタリングできる。
multilevel thresholding 2.14.2.4. adaptive thresholding画像中の小領域ごとにしきい値の値を計算する そのため領域ごとに光源環境が変わってしまうような画像に対して、単純なしきい値処理より良い結果が得られる
Adaptive Thresholding 2.14.3. モルフォロジー変換2.14.3.1. モルフォロジー変換とは?与えられた2値画像または濃淡画像からの特徴抽出を目的とした図形変形手法の理論体系 画像のノイズ除去,平滑化,形状記述,テクスチャ解析,細線化処理などに用いられる. Dilation, Erosion, Opening, Closingの基本演算と、モルフォロジーグラジエント(Dailationした画像からErosionした画像を減算することで,エッジを検出する) トップハット変換(元画像からOpeningした画像を減算する) ブラックハット変換(Closingした画像から元画像を減算する) の7種類 要は、以下の画像のように、対象画像を太くしたり補足したり、それらを組み合わせたりして目的のオブジェクトを取り出す方法(画像は上記リンクより拝借)
モルフォロジー変換 ある特徴的なオブジェクトを検出するのに使える。 see Hands-on Morphological Image Processing
2.14.3.2. モルフォロジ処理の具体的な処理とは?モルフォロジ処理は、“構造要素"と呼ばれる画像を移動させる要素と,“ミンコフスキー(Minkowski)和・ミンコフスキー差” と呼ばれる演算から成り立っている モルフォロジー処理における代表的な処理は、「Erosion(エロージョン)」と「Dilation(ダイレーション)」と呼ばれる処理 それぞれ孤立点の除去,不連続な点の接続と穴埋めのために利用される Erosionで細くしてノイズを減らして、Dilationで大きくして元の大きさに戻すイメージ
モルフォロジー変換の具体例 openingerosionの後にdilationなので、ノイズが消える closingdilationのあとにerosionなので、ノイズが増える 2.14.3.3. ミンコフスキー和と差ミンコフスキー和・差は、二つのベクトルの集合同士を使って新しいベクトルの集合を求める. このミンコフスキー和と差を用いることで、ゲームにおける衝突判定やロボットのモーションプログラミングで計算を簡単にできる.
ミンコフスキー差と和 2.14.4. ワープ処理 (Image warping)
ワープ処理 幾何学的変換は集合の何らかの幾何学的な構造を持つ自身への(もしくは幾何学的な構造を持つ相異なる集合への)全単射 アフィン変換も幾何学的変換の一つ
alt text 2.14.6. 統計的にノイズを消す方法 1
2
3
4
5
6
7
8
9
10
11
12
im = cv . imread ( "green-blurred.jpg" )
yuv = cv . cvtColor ( im , cv . COLOR_BGR2YCrCb ) # or COLOR_BGR2YUV, should work equivalently here
#uv = yuv[:,:,(2,3)]
mean = np . mean ( yuv , axis = ( 0 , 1 ))
std = np . std ( yuv , axis = ( 0 , 1 ))
#mask = ((yuv - mean) / std >= 4.5).any(axis=2)
# EDIT: there needs to be an abs() on that difference, we want magnitudes, no signs
mask = ( np . abs ( yuv - mean ) / std >= 4.5 ) . any ( axis = 2 )
mask_u8 = mask . astype ( np . uint8 ) * 255
#grayscale = (np.abs(yuv - mean) / std / 5).max(axis=2)
cv . imshow ( "mask" , mask_u8 )
cv . waitKey ( - 1 )
Copy
統計的なノイズ除去 2.14.7. 画素補間法(image interpolation)nearest neighbor interpolation 最近傍補間法とも コンピューターによる画像処理で、画像の回転・拡大・変形を行うときの画素補間法の一つ 求めたい画素に最も近い位置の画素の情報を参照して補間する
2.15. 距離2.15.1. 分布の距離2つの分布間の距離を出すKL距離などは厳密には距離ではないが、距離と言う。
KL距離 2.15.2. 一般的な距離マンハッタン距離 ユークリッド距離 マハラノビス距離 チェビシェフ距離
距離の比較 2.15.3. 文字列間の距離OCR後の文字列間の距離比較を行うには、次の距離が有名。
ハミング距離 2.16. 円2.16.1. Circle, Curve, Arc, Fircleの違いCircle: 円 Curve: 曲線 Arc: 円弧 Fircle: 手書きの円 メモ
トポロジー的には、Arcは図形では、凸 Circle -> D Cut -> Squaireのように相が変換する 2.16.2. RANSAC algorithmでArcフィッティングThe RANSAC (Random sample and consensus) algorithm is the gold standard in eliminating noise.
アルゴのフロー
データをランダムにサンプリングする.(非復元抽出) モデルを学習する 全てのデータに対して誤差を計算する 3で求めた誤差から正常値と外れ値を決める 正常値のみでモデルの性能を評価する 既定回数に達したら終了.そうでなければ1に戻る ようは、アンサンブル学習のbaggingをしている。
RANSACとGradient descent algoの違い。
RANSAC vs Gradient Descent Algo 2.16.3. ゼルニケ多項式 (Zernike Polynomials)とは?曲面を多項式で表現する手法の1つとして、光学設計ではZernike(ツェルニケ)多項式がよく使われる。詳細は以下のスライド。
2.16.4. 円形度確か、円形度はISOで定義があったはず。
円形度 = 4 π × S L 2
円形度 = \frac{4π \times S}{L^2}
円形度 = L 2 4 π × S
(S = 面積, L = 図形の周囲長)
その例。
cv2で円形度。
1
2
3
4
5
6
7
8
9
import cv2
import numpy as np
# contourは輪郭点のリスト cv2.findContours
# areaは図形の面積 cv2.contourArea
def calcCircleLevel ( contour , area ):
perimeter = cv2 . arcLength ( contour , True )
circle_level = 4.0 * np . pi * area / ( perimeter * perimeter ); # perimeter = 0 のとき気をつける
return circle_level
Copy 2.16.5. 円摩度 (アスペクト比で補正した円形度)see http://progearthplanetsci.org/highlights_j/072.html
New parameter of roundness R: Circularity corrected by aspect ratio.
R = C i r c u l a r i t y + ( C i r c u l a r i t y p e r f e c t _ c i r c l e − C i r c u l a r i t y a s p e c t _ r a t i o )
R = Circularity + (Circularity_{perfect\_circle} - Circularity_{aspect\_ratio})
R = C i rc u l a r i t y + ( C i rc u l a r i t y p er f ec t _ c i rc l e − C i rc u l a r i t y a s p ec t _ r a t i o )
ここで、
C i r c u l a r i t y p e r f e c t _ c i r c l e Circularity_{perfect\_circle} C i rc u l a r i t y p er f ec t _ c i rc l e C i r c u l a r i t y a s p e c t _ r a t i o Circularity_{aspect\_ratio} C i rc u l a r i t y a s p ec t _ r a t i o 真円のアスペクト比のみを変化させたときに得られる円形度 2.16.6. 円磨度 (psephicity) vs. roundness円磨度 (psephicity)礫(れき、小さい石のこと)または砂粒の円味を帯びる程度。 通常は構成岩石の比重と硬度との比をもって円磨率(coefficient of psephicity)という。 rounhdness砕屑物が川や海によって運搬される過程で破壊され摩滅によって次第に角や稜がとれて円い礫となる 円磨度の区分(Pettijohn : 1957)
角(angular,円磨度 : 0~0.15) 亜角(subangular,円磨度 : 0.15~0.25) 亜円(aubrounded,円磨度 : 0.25~0.40) 円(rounded,円磨度 : 0.40~0.60) 十分に円磨(well-rounded,円磨度 : 0.60~1) 2.16.7. 楕円の歪み度Major AxisとMinor Axisの比を取ればOK。
2.17. モーメントHuモーメントは次の3つに対して不変な特徴を持つ。
平行移動 (translation) スケール (scale, size) 回転 (rotation) 2.17.1. モーメント (素モーメント)2変数関数f ( x , y ) f(x,y) f ( x , y ) に対して、( p + q ) (p+q) ( p + q ) 次のモーメント(素モーメント)を次のように定義する。
pとqはそれぞれモーメントの次数。
M p q = ∫ − ∞ ∞ ∫ − ∞ ∞ x p y q f ( x , y ) d x d y
{M_{pq} = \int_{-\infty}^{\infty} \int_{-\infty}^{\infty} x^p y^q f(x,y) dx dy
}
M pq = ∫ − ∞ ∞ ∫ − ∞ ∞ x p y q f ( x , y ) d x d y
これをグレースケールの画像に適用すると
M i j = ∑ x ∑ y x i y j I ( x , y )
{M_{ij} = \sum_{x} \sum_{y} x^i y^j I(x,y)}
M ij = x ∑ y ∑ x i y j I ( x , y )
つまり、x 座標 × y 座標 × ピクセルの値 x座標 \times y座標 \times ピクセルの値 x 座標 × y 座標 × ピクセルの値 の合計値となる。
素モーメントそのものは不変量ではない。
M 00 = 面積 , M 01 = Y 軸座標値とピクセル値の面積 = Y 軸座標値の期待値 , M 10 = X 軸座標値とピクセル値の面積 = X 軸座標値の期待値 , M 11 = X Y 軸座標値とピクセル値の体積 = X と Y 軸座標の面積 , M 20 = Y 軸座標 値 2 とピクセル値の面積 = Y 軸座 標 2 の期待値 , M 02 = X 軸座標 値 2 とピクセル値の面積 = X 軸座 標 2 の期待値 ,
{\begin{align}
M_{00} &= 面積, \\
M_{01} &= Y軸座標値とピクセル値の面積=Y軸座標値の期待値, \\
M_{10} &= X軸座標値とピクセル値の面積=X軸座標値の期待値, \\
M_{11} &= XY軸座標値とピクセル値の体積=XとY軸座標の面積, \\
M_{20} &= Y軸座標値^2とピクセル値の面積=Y軸座標^2の期待値, \\
M_{02} &= X軸座標値^2とピクセル値の面積=X軸座標^2の期待値, \\
\end{align}}
M 00 M 01 M 10 M 11 M 20 M 02 = 面積 , = Y 軸座標値とピクセル値の面積 = Y 軸座標値の期待値 , = X 軸座標値とピクセル値の面積 = X 軸座標値の期待値 , = X Y 軸座標値とピクセル値の体積 = X と Y 軸座標の面積 , = Y 軸座標 値 2 とピクセル値の面積 = Y 軸座 標 2 の期待値 , = X 軸座標 値 2 とピクセル値の面積 = X 軸座 標 2 の期待値 ,
M 00 M_{00} M 00 の解釈すると、M 00 M_{00} M 00 などは、面積と捉えられる。 また、ピクセル値を確率と捉えると、面積ではなく、確率和 と理解できる。 M 01 M_{01} M 01 の解釈M 01 M_{01} M 01 はY軸座標値とピクセル値の掛け算=その面積。ただし、ピクセル値の値域[0,1]なので、確率と捉えると、 Y座標値の期待値の合計=期待値和
期待値 = 確率 × 確率変数
期待値=確率 \times 確率変数
期待値 = 確率 × 確率変数 合計なので、単位はC o u n t × E x p e c t e d V a l u e Count \times Expected Value C o u n t × E x p ec t e d Va l u e M 11 M_{11} M 11 の解釈M 11 M_{11} M 11 はXY軸座標値とピクセル値の掛け算=その体積。ただし、ピクセル値の値域[0,1]なので、確率と捉えると、 Y座標値の期待値とX軸座標値の期待値の積の合計=期待値積の和 =混合分布の和 M 02 M_{02} M 02 の解釈M 02 M_{02} M 02 はY軸座標値^2とピクセル値の掛け算=その体積。ただし、ピクセル値の値域[0,1]なので、確率と捉えると、 Y座標値^2の期待値の合計=期待値平方和 2.17.2. 中心モーメント(並行不変性)中心モーメントは平行普遍性をもつ。
なぜこの中心モーメントが並行不変性をもつかというと、ピクセル座標の原点を物体の中心(重心)にしているから。
なので、物体がどこにあろうが、同じ値を出す=並行不変値
μ i j = ∑ x ∑ y ( x − x ˉ ) i ( y − y ˉ ) j I ( x , y )
{\mu_{ij} = \sum_{x} \sum_{y} (x-\bar{x})^i (y-\bar{y})^j I(x,y)
}
μ ij = x ∑ y ∑ ( x − x ˉ ) i ( y − y ˉ ) j I ( x , y )
ただし、x ˉ , y ˉ \bar{x},\bar{y} x ˉ , y ˉ は先に示した重心を示す。
このμ \mu μ のパターンを定義すると以下。
μ 00 = M 00 , μ 01 = 0 , μ 10 = 0 , μ 11 = M 11 − x ˉ M 01 = M 11 − y ˉ M 10 , μ 20 = M 20 − x ˉ M 10 , μ 02 = M 02 − y ˉ M 01 ,
{\begin{align}
\mu_{00} &= M_{{00}}, \\
\mu_{01} &= 0, \\
\mu_{10} &= 0, \\
\mu_{11} &= M_{{11}}-{\bar {x}}M_{{01}}=M_{{11}}-{\bar {y}}M_{{10}} , \\
\mu_{20} &= M_{20}-{\bar {x}}M_{10}, \\
\mu_{02} &= M_{02}-{\bar {y}}M_{01}, \\
\end{align}}
μ 00 μ 01 μ 10 μ 11 μ 20 μ 02 = M 00 , = 0 , = 0 , = M 11 − x ˉ M 01 = M 11 − y ˉ M 10 , = M 20 − x ˉ M 10 , = M 02 − y ˉ M 01 ,
μ 01 \mu_{01} μ 01 の解釈μ 01 = 0 \mu_{01}=0 μ 01 = 0 の理由はy ˉ \bar{y} y ˉ がY軸の重心ピクセル座標であり、y − y ˉ y-\bar{y} y − y ˉ の値は+と-で分布して合計すると必ず0になるから。※ 重心はピクセルのブロブの原点。 μ 11 \mu_{11} μ 11 の解釈つまり、あるピクセルの値(確率)とそのピクセルの座標の原点からの距離を加味した指標 μ 20 \mu_{20} μ 20 の解釈重心から座標がどれだけ離れているか、ただし、二乗しているので、絶対値。 つまりは、重心からどれだけはなれているかを加味した値。 2.17.3. スケール不変性次のような値を考えることで、スケールに対する不変量を作ることができる。
このモーメントはi + j ≥ 2 i+j≥2 i + j ≥ 2 のとき並進不変性を持つ。
なぜ、スケール普遍化というと、分母に面積を撮っているから。つまり、単位面積あたりの値にしている。
η i j = μ i j μ 00 ( 1 + i + j 2 )
{\eta _{{ij}}={\frac {\mu _{{ij}}}{\mu _{{00}}^{{\left(1+{\frac {i+j}{2}}\right)}}}}
}
η ij = μ 00 ( 1 + 2 i + j ) μ ij
i + j < 2 i+j<2 i + j < 2 の理由上の定義を代入してみるとわかるが、i + j < 2 i+j<2 i + j < 2 のときは定数になってしまい意味を持たない。 なぜなら、下のように、μ \mu μ のijの合計が2未満のときは、分母を加味して定数だから。
μ 00 = 面積 μ 10 = 0 μ 01 = 0
{\begin{align}
\mu_{00}=面積 \\
\mu_{10}=0 \\
\mu_{01}=0 \\
\end{align}}
μ 00 = 面積 μ 10 = 0 μ 01 = 0 η 11 \eta_{11} η 11 の解釈
i = 1 j = 1 η i j = μ 11 μ 00 2
{\begin{align}
i=1 \\
j=1 \\
\eta_{ij}=\frac{\mu_{11}}{\mu_{00}^2}
\end{align}}
i = 1 j = 1 η ij = μ 00 2 μ 11 η 20 \eta_{20} η 20 の解釈
i = 2 j = 0 η i j = μ 20 μ 00 2
{\begin{align}
i=2 \\
j=0 \\
\eta_{ij}=\frac{\mu_{20}}{\mu_{00}^2}
\end{align}}
i = 2 j = 0 η ij = μ 00 2 μ 20 2.17.4. 回転不変性更にη i j \eta{ij} η ij を用いて、回転に対して不変な量を構築することができる。これはHuによって発見されたため、Huモーメント不変量と呼ばれている 1から6までは回転不変。7はskew invariant I 1 = η 20 + η 02
I_{1} = \eta _{{20}}+\eta _{{02}} \\
I 1 = η 20 + η 02
I 2 = ( η 20 − η 02 ) 2 + 4 η 11 2
I_{2} = (\eta _{{20}}-\eta _{{02}}) ^{2}+4 \eta _{{11}}^{2} \\
I 2 = ( η 20 − η 02 ) 2 + 4 η 11 2
I 2 = ( η 20 − η 02 ) 2 + 4 η 11 2
I_{2} = (\eta _{{20}}-\eta _{{02}})^{2}+4\eta _{{11}}^{2} \\
I 2 = ( η 20 − η 02 ) 2 + 4 η 11 2
I 3 = ( η 30 − 3 η 12 ) 2 + ( 3 η 21 − η 03 ) 2
I_{3} = (\eta _{{30}}-3\eta _{{12}})^{2}+(3\eta _{{21}}-\eta _{{03}})^{2} \\
I 3 = ( η 30 − 3 η 12 ) 2 + ( 3 η 21 − η 03 ) 2
I 4 = ( η 30 + η 12 ) 2 + ( η 21 + η 03 ) 2
I_{4} = (\eta _{{30}}+\eta _{{12}})^{2}+(\eta _{{21}}+\eta _{{03}})^{2} \\
I 4 = ( η 30 + η 12 ) 2 + ( η 21 + η 03 ) 2
I 5 = ( η 30 − 3 η 12 ) ( η 30 + η 12 ) [ ( η 30 + η 12 ) 2 − 3 ( η 21 + η 03 ) 2 ] + ( 3 η 21 − η 03 ) ( η 21 + η 03 ) [ 3 ( η 30 + η 12 ) 2 − ( η 21 + η 03 ) 2 ]
I_{5} = (\eta _{{30}}-3\eta _{{12}})(\eta _{{30}}+\eta _{{12}})[(\eta _{{30}}+\eta _{{12}})^{2}-3(\eta _{{21}}+\eta _{{03}})^{2}]+(3\eta _{{21}}-\eta _{{03}})(\eta _{{21}}+\eta _{{03}})[3(\eta _{{30}}+\eta _{{12}})^{2}-(\eta _{{21}}+\eta _{{03}})^{2}] \\
I 5 = ( η 30 − 3 η 12 ) ( η 30 + η 12 ) [( η 30 + η 12 ) 2 − 3 ( η 21 + η 03 ) 2 ] + ( 3 η 21 − η 03 ) ( η 21 + η 03 ) [ 3 ( η 30 + η 12 ) 2 − ( η 21 + η 03 ) 2 ]
I 6 = ( η 20 − η 02 ) [ ( η 30 + η 12 ) 2 − ( η 21 + η 03 ) 2 ] + 4 η 11 ( η 30 + η 12 ) ( η 21 + η 03 )
I_{6} = (\eta _{{20}}-\eta _{{02}})[(\eta _{{30}}+\eta _{{12}})^{2}-(\eta _{{21}}+\eta _{{03}})^{2}]+4\eta _{{11}}(\eta _{{30}}+\eta _{{12}})(\eta _{{21}}+\eta _{{03}}) \\
I 6 = ( η 20 − η 02 ) [( η 30 + η 12 ) 2 − ( η 21 + η 03 ) 2 ] + 4 η 11 ( η 30 + η 12 ) ( η 21 + η 03 )
I 7 = ( 3 η 21 − η 03 ) ( η 30 + η 12 ) [ ( η 30 + η 12 ) 2 − 3 ( η 21 + η 03 ) 2 ] − ( η 30 − 3 η 12 ) ( η 21 + η 03 ) [ 3 ( η 30 + η 12 ) 2 − ( η 21 + η 03 ) 2 ] .
I_{7} =(3\eta _{{21}}-\eta _{{03}})(\eta _{{30}}+\eta _{{12}})[(\eta _{{30}}+\eta _{{12}})^{2}-3(\eta _{{21}}+\eta _{{03}})^{2}]-(\eta _{{30}}-3\eta _{{12}})(\eta _{{21}}+\eta _{{03}})[3(\eta _{{30}}+\eta _{{12}})^{2}-(\eta _{{21}}+\eta _{{03}})^{2}]. \\
I 7 = ( 3 η 21 − η 03 ) ( η 30 + η 12 ) [( η 30 + η 12 ) 2 − 3 ( η 21 + η 03 ) 2 ] − ( η 30 − 3 η 12 ) ( η 21 + η 03 ) [ 3 ( η 30 + η 12 ) 2 − ( η 21 + η 03 ) 2 ] .
see https://stackoverflow.com/questions/11379947/meaning-of-the-seven-hu-invariant-moments-function-from-opencv
2.17.5. Humomentの例シンプルなHumomentによる類似画像検索。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import cv2
from math import log
def hudist ( f1 , f2 ):
def h2m ( x ):
if x == 0 :
return 0.0
elif x > 0 :
return 1.0 / log ( x )
else :
return - 1.0 / log ( - x )
def hum ( f ):
img = cv2 . imread ( f , cv2 . CV_LOAD_IMAGE_GRAYSCALE )
assert ( img != None ),( "failed to open %s " % f )
moments = cv2 . moments ( img )
return cv2 . HuMoments ( moments )
hu = hum ( f1 )
hu2 = hum ( f2 )
d = sum ([ abs ( h2m ( x1 ) - h2m ( x2 )) for ( x1 , x2 ) in zip ( hu . T [ 0 ], hu2 . T [ 0 ])])
return d
if __name__ == "__main__" :
imgs = [ 'Lenna.png' , 'Lena3.jpg' , 'sendo1.jpg' , 'Sendou2.jpg' , 'monnalisa.jpg' , 'apple.jpg' , 'Red_Apple.jpg' ]
for f1 in imgs :
print f1 ,
for f2 in imgs :
v = 100 * hudist ( f1 , f2 )
print ( " %5.2f " % v ),
print ""
Copy
元画像 Lenna.png Lena3.jpg sendo1.jpg Sendou2.jpg monnalisa.jpg apple.jpg Red_Apple.jpg Lenna.png 0.00 0.04 5.93 5.87 4.19 2.43 Lena3.jpg 0.04 0.00 5.93 5.86 4.22 2.41 sendo1.jpg 5.93 5.93 0.00 0.09 7.82 4.55 Sendou2.jpg 5.87 5.86 0.09 0.00 7.86 4.47 monnalisa.jpg 4.19 4.22 7.82 7.86 0.00 3.97 apple.jpg 2.43 2.41 4.55 4.47 3.97 0.00 Red_Apple.jpg 4.83 4.83 1.19 1.11 7.88 4.94
2.17.6. 画像のモーメントと面積と重心モーメントとは?
M n m = ∑ 画像全体 ( x 座 標 n × y 座 標 m × 画素値 )
M_{nm} = \sum^{画像全体} (x座標^{n} \times y座標^{m} \times 画素値)
M nm = ∑ 画像全体 ( x 座 標 n × y 座 標 m × 画素値 )
画像中の全ピクセルについての総和。
2.17.7. モーメントのフローモーメントの計算方法は2つのフローに従う。
ピクセルずつデータを取る 座標ごとのべき乗をもとめて足す まず、データ(x座標、y座標、画素値)を調べる
次に、全てのピクセルについて次を求める。
さらに、上記の2つと画素値(0または1)を掛け合わせる。 例えば、(15,17,1)のピクセルについて、n=2、m=0の場合は次となる。
M 2 , 0 = 1 5 2 × 1 7 0 × 1 = 225 × 1 × 1 = 225
M_{2,0} = 15^{2} \times 17^{0} \times 1 = 225 \times 1 \times 1 = 225
M 2 , 0 = 1 5 2 × 1 7 0 × 1 = 225 × 1 × 1 = 225
このピクセルでの計算結果は225となる。
全てのピクセルでの計算結果を足し合わせたものが画像のモーメントM n m M_{nm} M nm 。
単純化すると次になる。
画像なので座標はピクセル座標 (0,0), (2,2)の範囲のピクセルを考える つまり、範囲はグリッド座標でいう、(0,0) から (3,3)までのブロブ 面積は9M 0 , 0 M_{0,0} M 0 , 0 = 9M 1 , 0 M_{1,0} M 1 , 0 = 0+1+2+0+1+2+0+1+2 = 9 (左上から横方向にチェック)M 10 M 00 \frac{M_{10}}{M_{00}} M 00 M 10 =9/9=1 つまり、X座標では1の座標をもつピクセルが重心。
また、重心=ブロブの原点。
つまり、重心を原点ととると、全てのx座標の合計、全てのy座標の合計のそれぞれが0となる。
2.18. Contour(輪郭)2.18.1. contourとperimeterの違い2.18.2. Contourのフローグレースケールに変換 画像を2値化する 輪郭の取得 輪郭の分析 例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import cv2
import numpy as np
import matplotlib.pyplot as plt
from skimage import sio
url = 'https://upload.wikimedia.org/wikipedia/commons/5/53/OpenCV_Logo_with_text.png'
img = sio . imread ( url )
gray = cv2 . cvtColor ( img , cv2 . COLOR_BGR2GRAY )
ret , binary = cv2 . threshold ( gray , 150 , 255 , cv2 . THRESH_BINARY )
contours , hierarchy = cv2 . findContours ( binary , cv2 . RETR_TREE , cv2 . CHAIN_APPROX_SIMPLE )
img_blank = np . ones_like ( img ) * 255
img_contour_only = cv2 . drawContours ( img_blank , contours , - 1 , ( 0 , 0 , 0 ), 3 )
plt . imshow ( cv2 . cvtColor ( img_contour_only , cv2 . COLOR_BGR2RGB ))
plt . show ()
Copy 2.18.3. Contourから取れるモノ面積(cv2.contourArea
) サイズ(cv2.boundingRect
) 点の数 領域を囲む周囲長(cv2.arcLength
) 領域を塗りつぶし(cv2.drawContours
) 少ない座標で輪郭(cv2.approxPolyDP
) 輪郭の近似による輪郭の取得(cv2.approxPolyDP(cnt,0.1*cv2.arcLength(cnt,True),True)
) 凸包による図形判別(cv2.convexHull
) 外接矩形による角度取得(cv2.minAreaRect
) 輪郭の類似度の計算(cv2.matchShapes
) 重複したcontoursのマージ(NMS:Non-Maximum Suppression的な
) 隣接するcontoursのマージ(階層クラスタリング
) 2.18.4. findContoursの引数binaryデータを渡して、その中のオブジェクトを取得するメソッド 簡易的物体検知であり、以外に複雑な構造をしているのでメモ 手法(mode)+輪郭取り方(method)でパターンが分かる
入力画像 NOTE: 外の白枠は分かりやすさでついているもので、実際はない。
2.18.4.1. findCountoursのmodeRETR_TREE: 輪郭の階層情報をツリー形式で取得する。
RETR_TREEの結果
RETR_TREEの構造 RETR_CCOMP:白の輪郭の外側と内側の輪郭を構造化する
RETR_CCOMPの結果
RETR_CCOMPの構造 RETR_LIST:階層なし
RETR_LISTの結果
RETR_LISTの構造 RETR_EXTERNAL:外側のみ
RETR_EXTERNALの結果
RETR_EXTERNALの構造 2.18.4.2. findCountoursのmethodCHAIN_APPROX_NONE:輪郭上のすべての座標を取得
CHAIN_APPROX_NONEの例 CHAIN_APPROX_SIMPLE:縦、横、斜め45°方向に完全に直線の部分の輪郭の点を省略
CHAIN_APPROX_SIMPLEの例 CHAIN_APPROX_TC89_L1, CHAIN_APPROX_TC89_KCOS:輪郭の座標を直線で近似できる部分の輪郭の点を省略
CHAIN_APPROX_TC89_L1, CHAIN_APPROX_TC89_KCOSの例 2.18.4.3. findContoursの戻り値methodの設定により輪郭番号があったりなかったりするが、大体contoursは次の構造を取る。
1
contours[輪郭番号][点の番号][0][X座標, Y座標]
Copy minとmaxで4点のcontourを作る例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
def create_contour_from_bbox ( minx , miny , maxx , maxy ):
contour = np . array ([
[[ minx , miny ]],
[[ minx , maxy ]],
[[ maxx , maxy ]],
[[ maxx , miny ]]
])
return [ contour ]
# 使用例
minx , miny , maxx , maxy = 100 , 50 , 200 , 150
contour = create_contour_from_bbox ( minx , miny , maxx , maxy )
Copy hierarchyは次の構造を取る。
1
2
3
4
hierarchy[0][輪郭番号][次の輪郭番号, 前の輪郭番号, 最初子供(内側)の輪郭番号, 親(外側)の輪郭番号]
NOTE:
- 該当する輪郭番号が無い時は-1
Copy
階層構造の例 上の場合は下になる。 分かりやすく言うと、階層的には、下、上、右、左の順番。
1
2
3
4
5
6
7
8
9
10
hierarchy[0][0] は [ 3, -1, 1, -1]
hierarchy[0][1] は [-1, -1, 2, 0]
hierarchy[0][2] は [-1, -1, -1, 1]
hierarchy[0][3] は [-1, 0, 4, -1]
hierarchy[0][4] は [-1, -1, 5, 3]
hierarchy[0][5] は [ 6, -1, -1, 4]
hierarchy[0][6] は [-1, 5, 7, 4]
hierarchy[0][7] は [-1, -1, 8, 6]
hierarchy[0][8] は [ 9, -1, -1, 7]
hierarchy[0][9] は [-1, 8, -1, 7]
Copy 2.18.5. Floodfill他の物体検知手法としては、Floodfillもある seedPoint に指定した画素と画素値の差が指定範囲内の画素を同じグループ (連結成分) と見なして塗りつぶしを行う loDiff(下限)とupDiff(上限)をつけて塗りつぶすアルゴ
floodfill 2.18.6. オプティカルフロー物体の移動軌跡を行うのに使う手法 動画像中のフレームで、移動体上の各点の移動方向と移動距離を表す量である動きベクトルを計算する 物体トラッキングでは、カルマンフィルタを使って予測することも行う
オプティカルフロー 2.19. ガイド2.19.1. Scikit learn ガイド有名なScikit-leranのガイド。
ML Map 2.19.2. Dlib選択ガイドこちらは一部で有名なDlibガイド。
Dlib Guide 3. スニペット3.1. 簡易的なログ残し1
2
3
4
5
6
7
8
9
import codecs
from datetime import datetime , timedelta , timezone
JST = timezone ( timedelta ( hours =+ 9 ), 'JST' )
def print_log ( message ):
now = datetime . now ( JST )
print ( f "{now}: {message}" )
print ( f "{now}: {message}" , file = codecs . open ( 'log.txt' , 'a' , 'utf-8' ))
Copy 3.2. シードの設定 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import os
import random
import numpy as np
import torch
from torch.backends import cudnn
def set_seed ( seed = 42 ):
os . environ [ "PYTHONHASHSEED" ] = str ( seed )
random . seed ( seed )
np . random . seed ( seed )
torch . manual_seed ( seed )
if torch . cuda . is_available ():
torch . cuda . manual_seed ( seed )
cudnn . benchmark = True
cudnn . deterministic = True
Copy 3.3. 画像の変換基本はPillow形式だが、numpyやtorchの形式を相互に変換する必要がある。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
import math
from typing import Union
import cv2
import numpy as np
import torch
from PIL import Image
from skimage.metrics import structural_similarity
from torchvision import transforms
class CvUtil ():
@staticmethod
def cv_bgr_to_cv_rgb ( img_bgr : np . ndarray ) -> np . ndarray :
return cv2 . cvtColor ( img_bgr , cv2 . COLOR_BGR2RGB )
@staticmethod
def cv_bgr_to_cv_gray ( img_bgr : np . ndarray ) -> np . ndarray :
return cv2 . cvtColor ( img_bgr , cv2 . COLOR_BGR2GRAY )
@classmethod
def cv_bgr_to_pil ( cls , img_bgr : np . ndarray ) -> Image . Image :
"""
ref https://discuss.pytorch.org/t/how-to-transform-images-to-tensors/71447
"""
img_rgb = cls . cv_bgr_to_cv_rgb ( img_bgr )
return Image . fromarray ( img_rgb ) . convert ( 'RGB' )
@staticmethod
def cv_to_pil ( image : np . ndarray ) -> Image . Image :
'''
OpenCV型 -> PIL型
NOTE: cv2のはBGR前提
ref: https://qiita.com/derodero24/items/f22c22b22451609908ee
'''
new_image = image . copy ()
if new_image . ndim == 2 : # モノクロ
pass
elif new_image . shape [ 2 ] == 3 : # カラー
new_image = cv2 . cvtColor ( new_image , cv2 . COLOR_BGR2RGB )
elif new_image . shape [ 2 ] == 4 : # 透過
new_image = cv2 . cvtColor ( new_image , cv2 . COLOR_BGRA2RGBA )
new_image = Image . fromarray ( new_image )
return new_image
class PilUtil ():
@staticmethod
def pil_to_normalized_tensor ( img_pil : Image . Image ) -> torch . Tensor :
# MNISTは ToTensor() すると [0, 1] になるので0.5を引いて0.5で割って [-1, 1] の範囲に変換する。
normalize = transforms . Normalize (
mean = [ 0.5 ],
std = [ 0.5 ]
) # z = (x - mean) / std
img_transform = transforms . Compose ([
transforms . Resize (( 256 , 256 )),
transforms . Grayscale (),
transforms . ToTensor (), # [0, 255] => [0, 1]
# normalize, # [0, 1] => [-1, 1]
])
return img_transform ( img_pil )
@staticmethod
def pil_to_tensor ( img_pil : Image . Image ) -> torch . Tensor :
"""
ref https://discuss.pytorch.org/t/pytorch-pil-to-tensor-and-vice-versa/6312/9
"""
return transforms . ToTensor ()( img_pil ) . unsqueeze_ ( 0 )
@staticmethod
def pil_to_pil_gray ( img_pil : Image . Image ) -> Image . Image :
"""
グレースケール化
"""
return img_pil . convert ( 'L' )
@staticmethod
def pil_to_pil_bw ( img_pil : Image . Image ) -> Image . Image :
"""
2値化
"""
return img_pil . convert ( '1' )
@staticmethod
def pil_to_cv ( image : Image . Image ) -> np . ndarray :
'''
PIL型 -> OpenCV型
NOTE: cv2のはBGRになる
'''
new_image = np . array ( image , dtype = np . uint8 )
if new_image . ndim == 2 : # モノクロ
pass
elif new_image . shape [ 2 ] == 3 : # カラー
new_image = cv2 . cvtColor ( new_image , cv2 . COLOR_RGB2BGR )
elif new_image . shape [ 2 ] == 4 : # 透過
new_image = cv2 . cvtColor ( new_image , cv2 . COLOR_RGBA2BGRA )
return new_image
class TensorUtil ():
@staticmethod
def tensor_to_pil ( img_tensor : torch . Tensor ) -> Image . Image :
"""
ref https://discuss.pytorch.org/t/pytorch-pil-to-tensor-and-vice-versa/6312/9
"""
return transforms . ToPILImage ()( img_tensor . squeeze_ ( 0 ))
@staticmethod
def np_2d_to_pil_bw_img ( np_2d : np . ndarray ) -> Image . Image :
"""
ref: https://stackoverflow.com/questions/37558523/converting-2d-numpy-array-of-grayscale-values-to-a-pil-image
"""
return Image . fromarray ( np . uint8 ( np_2d * 255 ), 'L' )
@staticmethod
def tensor_2d_to_pil_bw_img ( tensor_2_axis : torch . Tensor ) -> Image . Image :
"""
2次元のテンソルを白黒のPILの画像に変換
ref: https://discuss.pytorch.org/t/pytorch-pil-to-tensor-and-vice-versa/6312/8
"""
tensor_3_axis = TensorUtil . torch_add_dim ( tensor_2_axis )
im = transforms . ToPILImage ()( tensor_3_axis ) . convert ( "L" )
return im
@staticmethod
def tensor_to_np ( x_tensor : torch . Tensor ) -> np . ndarray :
# numpyはCPUのメモリを使うので、一旦CPUに落とす
device = x_tensor . device
# NOTE: ndarrayの場合はコピーはcopy()
x_np = x_tensor . to ( 'cpu' ) . detach () . numpy () . copy ()
x_tensor = x_tensor . to ( device ) # テンソルを元にもどス
return x_np
@staticmethod
def tensor_to_np_gray ( x_tensor : torch . Tensor ) -> np . ndarray :
"""
TODO: 一つのテンソルのみを受け取るように修正するべき
tensorを白黒の画像に変換する
tensorの構造は: (channnel, width, height)
"""
# x_tensors = 0.5 * (x_tensors + 1) # [-1,1] => [0, 1]
x_tensor = x_tensor . clamp ( 0 , 1 ) # np.clipと同じ、[0, 1]にデータを収める
x_tensor = x_tensor . view ( 1 , 256 , 256 )
img_gray = x_tensor . cpu () . numpy ()
return img_gray
@staticmethod
def torch_remove_dim ( x_tensor : torch . Tensor ) -> torch . Tensor :
"""
tensorの次元削除。データローダーのテンソル -> 画像化の時によく使う
"""
return x_tensor . squeeze ()
@staticmethod
def torch_add_dim ( x_tensor : torch . Tensor ) -> torch . Tensor :
"""
tensorの次元拡張。画像 -> データローダーのデータ化の時によく使う
"""
return torch . unsqueeze ( x_tensor , 0 )
@classmethod
def torch_create_dummy_2dim ( cls , x_tensor : torch . Tensor ) -> torch . Tensor :
"""
x_tensor: [1, x, x]
バッチノーマライゼーションをモデルで入れている時に使用する。
1つのtensorだとエラーが出てしまうので、便宜上形式的に同じ画像を入れて回避する。
ref: https://discuss.pytorch.org/t/appending-to-a-tensor/2665/11
"""
x1_tensor = cls . torch_add_dim ( x_tensor )
x2_tensor = cls . torch_add_dim ( x_tensor )
return torch . cat ([ x1_tensor . clone (), x2_tensor . clone ()], dim = 0 )
class NumpyUtil ():
@staticmethod
def np_to_tensor ( x_np : np . ndarray ) -> torch . Tensor :
"""
ref: https://tzmi.hatenablog.com/entry/2020/02/16/170928
"""
x_np = x_np . astype ( np . float32 )
# NOTE: tensorの場合はコピーはclone()
x_tensor = torch . from_numpy ( x_np ) . clone ()
device = 'cuda' if torch . cuda . is_available () else 'cpu'
x_tensor = x_tensor . to ( device )
return x_tensor
@staticmethod
def np_add_dim ( np_2d_arr : np . ndarray ) -> np . ndarray :
"""
軸を増やす。使用用途としては、二次元配列を白黒画像化したいときなど。
"""
return np . expand_dims ( np_2d_arr , 0 )
class TypeCheckUtil ():
@staticmethod
def is_pil ( any_img ) -> bool :
return isinstance ( any_img , Image . Image )
@staticmethod
def is_np ( any_img ) -> bool :
"""
npかどうかのみ
"""
return isinstance ( any_img , np . ndarray )
@staticmethod
def is_tensor ( any_img ) -> bool :
return isinstance ( any_img , torch . Tensor )
def is_cv ( any ) -> bool :
"""
TODO
"""
pass
class ImageUitl ():
@staticmethod
def get_img_info ( any_img ):
"""
Dimensions-of-an-input-image
"""
if isinstance ( any_img , Image . Image ): # PIL
w , h = any_img . size
return h , w
elif isinstance ( any_img , torch . Tensor ): # Torch
# https://discuss.pytorch.org/t/dimensions-of-an-input-image/19439
c , h , w = any_img . size ()
return h , w
elif np . shape ( any_img ) == (): # OPENCV
# https://answers.opencv.org/question/185629/in-python-is-there-a-method-or-operator-to-determine-if-a-frame-is-empty-or-an-all-zero-matrix/
h , w , c = any_img . shape
return h , w
else :
raise AssertionError ( "it's not compatible image type" )
@staticmethod
def split_img_horizontally ( img_cv_bgr : np . ndarray , n : int ) -> np . ndarray :
"""
n枚に画像を分割する
"""
if n is None :
raise AssertionError ( "Failed to split image." )
height , width , _ = img_cv_bgr . shape
blob_width = math . floor ( width / n )
y1 = 0
y2 = height
buff = []
for i in range ( n ):
x1 = i * blob_width
x2 = ( i + 1 ) * blob_width
c = img_cv_bgr [ y1 : y2 , x1 : x2 ] # img_rgb.crop((x1, y1, x2, y2))
buff . append ( c )
return buff
@staticmethod
def img_save ( any_img : Union [ np . ndarray , Image . Image ], file_path : str ):
if TypeCheckUtil . is_pil ( any_img ):
any_img . save ( file_path )
elif TypeCheckUtil . is_np ( any_img ):
cv2 . imwrite ( file_path , any_img )
else :
raise AssertionError ( "failed to get image" )
@staticmethod
def mse ( x , y ):
return np . linalg . norm ( x - y )
@staticmethod
def ssim ( input_img , output_img , kernel_size = 15 , is_data_range = False ):
kernel = np . ones (( kernel_size , kernel_size ), np . float32 ) / 255
output_img = cv2 . filter2D ( output_img , - 1 , kernel )
input_img = cv2 . filter2D ( input_img , - 1 , kernel )
if is_data_range :
return structural_similarity (
input_img ,
output_img ,
data_range = input_img . max () - input_img . min (),
)
else :
return structural_similarity (
input_img ,
output_img ,
)
Copy 3.4. borderの追加1
2
3
4
import cv2
img = cv2 . imread ( './fig.png' )
img_pad = cv2 . copyMakeBorder ( img , 50 , 50 , 50 , 50 , cv2 . BORDER_CONSTANT , ( 0 , 0 , 0 ))
cv2 . imwrite ( './fig_pad.png' , img_pad )
Copy
3.5. torchのdatasetの画像確認 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import matplotlib.pyplot as plt
import numpy as np
from torch.utils.data import DataLoader
def confirm_img ( train_loader : DataLoader ):
images , labels = iter ( train_loader ) . next ()
images = images . clamp ( 0 , 1 ) # np.clipと同じ、[0, 1]にデータを収める
images = images . view ( images . size ( 0 ), 1 , 256 , 256 )
image_gray = images [ 0 ] . squeeze () . cpu () . numpy ()
plt . imshow ( image_gray , cmap = "gray" )
plt . show ()
plt . hist ( images [ 0 ] . flatten (), bins = 256 , range = ( 0 , 1 ))
plt . show ()
Copy 3.6. Contourの確認スニペット 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import math
import random
import numpy as np
import cv2
import matplotlib.pyplot as plt
def show_image ( img_bgr , title ):
plt . imshow ( img_bgr )
plt . title ( title )
plt . show ()
def show_contours_with_multi_colors ( contours , img_bgr , title ):
for cnt in contours :
color = [ random . randinit ( 0 , 2455 ) for _ in range ( 3 )]
cv2 . drawContours ( img_bgr , [ cnt ], - 1 , color , - 1 )
plt . imshow ( img_bgr )
plt . title ( title )
plt . show ()
def show_contours_with_one_color ( contours , img_bgr , title ):
cv2 . drawContours ( img_bgr , [ contours ], - 1 , ( 0 , 255 , 0 ), - 1 )
plt . imshow ( img_bgr )
plt . title ( title )
plt . show ()
def show_all_contours ( contours , img_bgr ):
for cnt in contours :
cv2 . drawContours ( img_bgr , [ cnt ], - 1 , ( 0 , 0 , 0 ), - 1 )
plt . show ()
Copy 3.7. 階層的クラスタリングCVでの階層的クラスタリング(agglomerative clustering algorithm)は、Contourのクラスタリングをする手法 その際のbboxの距離は、NMSやIoUなどとは違い、距離自体は、サイズを加味して計算する 基本的に閾値以下で隣接するモノをまとめる手法 すべてのcontourについて比較する必要がある為、計算量Oは最低でもO ( n 2 ) O(n^2) O ( n 2 ) になるはず 実際は一次元だが、イメージはDendrogramで、距離が閾値以下がまとまる
Dendrogram クラスターの結合方法は次の4つがあるが、このアルゴは3番の最短距離法を利用している。
ウォード法:クラスターの併合時、平方和が最小になるようにする方法 最長距離法:距離の最も遠いデータ同士から併合していくする方法 最短距離法:距離の最も近いデータ同士から併合していく方法 群平均法:すべてのデータの平均を計算し、クラスターの距離として併合する方法
ウォード法
最長距離法
最短距離法
郡平均法 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import os
import cv2
import numpy
def calculate_contour_distance ( contour1 , contour2 ):
x1 , y1 , w1 , h1 = cv2 . boundingRect ( contour1 )
c_x1 = x1 + w1 / 2
c_y1 = y1 + h1 / 2
x2 , y2 , w2 , h2 = cv2 . boundingRect ( contour2 )
c_x2 = x2 + w2 / 2
c_y2 = y2 + h2 / 2
# NOTE:
# - XとYについての距離の最大値を取る
# - 項事に考える
# - a = abs(c_x1 - c_x2) : 2つのbboxの中心点の距離
# - b = (w1 + w2)/2 : 2つのbboxの平均幅
# - c = a - b : 2つのbboxのサイズを加味した距離(同じ中心点の距離でも、小さいbbox程離れて、多いbbox程近くなる)
return max ( abs ( c_x1 - c_x2 ) - ( w1 + w2 ) / 2 , abs ( c_y1 - c_y2 ) - ( h1 + h2 ) / 2 )
def merge_contours ( contour1 , contour2 ):
return numpy . concatenate (( contour1 , contour2 ), axis = 0 )
def agglomerative_cluster ( contours , threshold_distance = 40.0 ):
current_contours = contours
while len ( current_contours ) > 1 :
min_distance = None
min_coordinate = None
for x in range ( len ( current_contours ) - 1 ):
for y in range ( x + 1 , len ( current_contours )):
distance = calculate_contour_distance ( current_contours [ x ], current_contours [ y ])
if min_distance is None :
min_distance = distance
min_coordinate = ( x , y )
elif distance < min_distance :
min_distance = distance
min_coordinate = ( x , y )
if min_distance < threshold_distance :
index1 , index2 = min_coordinate
current_contours [ index1 ] = merge_contours ( current_contours [ index1 ], current_contours [ index2 ])
del current_contours [ index2 ]
else :
break
return current_contours
Copy 結果
Contour Clustering 3.8. 二値化の確認二値化は必須スキルなので、一覧にして確認する為のスニペット。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import cv2
import numpy as np
import matplotlib.pyplot as plt
image_path = "test.png"
image = cv2 . imread ( image_path , cv2 . IMREAD_GRAYSCALE )
# image = np.random.randint(0, 256, (100, 100), dtype=np.uint8)
gray_image = cv2 . cvtColor ( cv2 . cvtColor ( image , cv2 . COLOR_GRAY2BGR ), cv2 . COLOR_BGR2GRAY )
histogram = cv2 . calcHist ([ gray_image ], [ 0 ], None , [ 256 ], [ 0 , 256 ])
_ , otsu_thresh = cv2 . threshold ( gray_image , 0 , 255 , cv2 . THRESH_BINARY + cv2 . THRESH_OTSU )
adaptive_mean = cv2 . adaptiveThreshold ( gray_image , 255 , cv2 . ADAPTIVE_THRESH_MEAN_C , cv2 . THRESH_BINARY , 11 , 2 )
adaptive_gaussian = cv2 . adaptiveThreshold ( gray_image , 255 , cv2 . ADAPTIVE_THRESH_GAUSSIAN_C , cv2 . THRESH_BINARY , 11 , 2 )
fig_adjusted , axes_adjusted = plt . subplots ( 6 , 5 , figsize = ( 20 , 24 ))
for i , ax in enumerate ( axes_adjusted . flatten ()[: 25 ]):
_ , thresh_image = cv2 . threshold ( gray_image , i * 10 , 255 , cv2 . THRESH_BINARY )
ax . imshow ( thresh_image , cmap = 'gray' , vmin = 0 , vmax = 255 )
ax . set_title ( f 'Threshold = {i * 10}' )
ax . axis ( 'off' )
ax_hist_adjusted = plt . subplot2grid (( 6 , 5 ), ( 5 , 0 ), colspan = 3 , fig = fig_adjusted )
ax_hist_adjusted . plot ( histogram )
ax_hist_adjusted . set_title ( 'Grayscale Histogram' )
ax_hist_adjusted . set_xlabel ( 'Pixel value' )
ax_hist_adjusted . set_ylabel ( 'Frequency' )
ax_otsu_adjusted = plt . subplot2grid (( 6 , 5 ), ( 5 , 3 ), fig = fig_adjusted )
ax_otsu_adjusted . imshow ( otsu_thresh , cmap = 'gray' )
ax_otsu_adjusted . set_title ( 'Otsu Thresholding' )
ax_otsu_adjusted . axis ( 'off' )
ax_adaptive_mean_adjusted = plt . subplot2grid (( 6 , 5 ), ( 5 , 4 ), fig = fig_adjusted )
ax_adaptive_mean_adjusted . imshow ( adaptive_mean , cmap = 'gray' )
ax_adaptive_mean_adjusted . set_title ( 'Adaptive Mean Thresholding' )
ax_adaptive_mean_adjusted . axis ( 'off' )
plt . tight_layout ()
fig_adjusted . savefig ( 'binarization.png' )
Copy 元画像
結果
4. まとめCV周りは理解しなければならない事が多い 上記の通り、古典的なCVのアルゴでも、手法も多岐に渡る Deep learning, ViTとCVの最新技術はまだまだ続く 5. 参考文献