python

画像処理・解析その4

特徴抽出

そもそも特徴とは、パターン認識に役立つ情報量の多い部分のことです。
情報量の多い部分とはどういった物を指すのかというと、例えば画像の中に認識したい物体として車があった場合、車以外の背景だったり人という情報は必要がないので、車のボディやフロント部分が情報量の多い部分になります。
情報量が多い部分をもう少し大別すると、コーナー > エッジ > フラットという順で重要度が変化します。
フラットは輝度の変化がすくない物、エッジは前回やりましたが、x軸 or y軸に変化がある物、コーナーはx軸とy軸どちらとも変化がある物です。

次にアルゴリズムを見ていきます(線形代数の知識が必要です)。
フラットは画像の中に方向性がありません。
方向性がないというのは、対角化によって固有値と固有ベクトルを取得し、全ての要素が少しずつ入っている状態になります。
エッジがある場合には、x軸もしくはy軸に対する固有ベクトルが大きくなり、可視化すれば一つの固有ベクトルが大きくなります。
コーナーの場合には、エッジが2つあるので、大きくなっている固有ベクトルが複数見つかります。

上記のように固有値の大きい固有ベクトルの個数で画像の特徴を抽出できます。
やり方としては。Harrisのコーナー検出などがあります。

耐性速度ライセンス備考
Harris回転スケールが変わると精度が落ちる
SIFT回転・スケール・大きさXX特徴量が128次元=>メモリ負荷大
特許取得のため商業利用不可
SURF回転・スケール・大きさXSIFTを高速化
特許取得のため商業利用不可
ORB回転・スケール・大きさ特徴量を2値化=速い
特許問題がない
KAZE回転・スケール・大きさXGaussianカーネルではなく
非線形フィルタを使用
AKAZE回転・スケール・大きさKAZEを高速化

試すのであれば、ORB、KAZE、AKAZEがお手軽です。
ここの表以外にもたくさんあるので興味がある人は調べてみてください。

import cv2, copy
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

img = cv2.cvtColor(cv2.imread('Lenna_(test_image).png'), cv2.COLOR_BGR2RGB) 
img_g = cv2.imread('Lenna_(test_image).png', 0)

img_harris = copy.deepcopy(img)
# 2はブロックサイズ:画像をどれ位の大きさで区切るか
# 3はカーネルサイズ:Sobelフィルターの大きさ
# 0.04はブロックサイズとカーネルサイズに与える影響範囲:基本的に0.04~0.05で問題なし
img_dst = cv2.cornerHarris(img_g, 2, 3, 0.04) # 特徴点の情報が格納

# 最大のコーナーの値を検出
img_harris[img_dst > 0.05 * img_dst.max()] = [255, 0, 0] # 閾値の設定

fig = plt.figure(figsize=(15, 9))
plt.imshow(img_harris)

# kaze
img_kaze = copy.deepcopy(img)
kaze = cv2.KAZE_create() # 特徴抽出機をインスタンス化
kp1 = kaze.detect(img, None) # 特徴点を格納
img_kaze = cv2.drawKeypoints(img_kaze, kp1, None)

fig = plt.figure(figsize=(15, 9))
plt.imshow(img_kaze)

# akaze(インスタンスが変わっただけ)
img_akaze = copy.deepcopy(img)
akaze = cv2.AKAZE_create() # 特徴抽出機をインスタンス化
kp2 = akaze.detect(img, None) # 特徴点を格納
img_akaze = cv2.drawKeypoints(img_akaze, kp2, None)

fig = plt.figure(figsize=(15, 9))
plt.imshow(img_akaze)

# orb(インスタンスが変わっただけ)
img_orb  = copy.deepcopy(img)
orb = cv2.ORB_create()
kp3 = orb.detect(img_orb)
img_orb  = cv2.drawKeypoints(img_orb, kp3, None)

fig = plt.figure(figsize=(15, 9))
plt.imshow(img_orb)

顔検出

OpenCVには顔の検出器が実装されているので、それを使用することで簡単に実装ができます。
1点だけ準備が必要でして、特徴ファイルを用意する必要があります。
これは下記のリンクから取得できます。
https://github.com/opencv/opencv/tree/master/data/haarcascades

import cv2
import matplotlib.pyplot as plt
%matplotlib inline

# 人の顔を検出するHAAR FILEを使用
haar  = './opencv/data/haarcascades/haarcascade_frontalface_default.xml'
cascade = cv2.CascadeClassifier(haar)

img = cv2.cvtColor(cv2.imread('Lenna_(test_image).png'), cv2.COLOR_BGR2RGB)
img_g = cv2.imread('Lenna_(test_image).png', 0)

face = cascade.detectMultiScale(img_g)
# 画像の顔部分を検知したx,y座標とwidthとheightが返ってくる
# 複数検知すれば、複数の値が格納される
# face => array([[221, 205, 166, 166]], dtype=int32)

for x, y, w, h in face:
    cv2.rectangle(img, (x, y), (x+w, y+h), (0, 0, 255), 1)
    
fig = plt.figure(figsize=(15, 9))
plt.imshow(img)

ブロブ検出

ブロブはオブジェクトの数を認識し、重心や面積を返してくれます。

import cv2
import copy
import matplotlib.pyplot as plt
%matplotlib inline

img = cv2.cvtColor(cv2.imread('blob.png'), cv2.COLOR_BGR2RGB)
img_g = cv2.imread('blob.png', 0)

# ブロブは2値化が必須
ret, img_bi = cv2.threshold(img_g, 100, 255, cv2.THRESH_BINARY)
plt.gray()
plt.imshow(img_bi)

# ブロブは様々な返り値が返ってくる
nLabels, labelImage, stats, centrois = cv2.connectedComponentsWithStats(img_bi)

# オブジェクトに加えて背景も数えてくれる
nLabels # 5

# 座標に対応しており、id(オブジェクト)ごとに数値が割り振られる
labelImage
# array([[0, 0, 0, ..., 0, 0, 0],
#        [0, 0, 0, ..., 0, 0, 0],
#        [0, 0, 0, ..., 0, 0, 0],
#        ...,
#        [0, 0, 0, ..., 0, 0, 0],
#        [0, 0, 0, ..., 0, 0, 0],
#        [0, 0, 0, ..., 0, 0, 0]], dtype=int32)

# オブジェクトの座標とwidth,heightと面積が格納される
stats
# array([[      0,       0,    2396,    1442, 2949308],
#        [   1224,     129,     547,     467,  129733],
#        [    376,     210,     235,     226,   40280],
#        [    237,     704,     504,     545,  200549],
#        [   1571,     860,     451,     431,  135162]], dtype=int32)

img_blob = copy.deepcopy(img)
h, w = img_g.shape
color = [[255,0,0],[0,255,0], [0,0,255], [255, 255,0]]

for y in range(h):
    for x in range(w):
        if labelImage[y, x] > 0: #背景除外
            img_blob[y, x] = color[labelImage[y, x] -1]
            
for i in range(1, nLabels):
    xc = int(centrois[i][0])
    yc = int(centrois[i])
    font = cv2.FONT_HERSHEY_COMPLEX
    scale = 1
    color = (255, 255, 255)
    cv2.putText(img_blob, str(stats[i][-1]), (xc, yc), font, scale, color)

fig = plt.figure(figsize=(15, 9))
plt.imshow(img_blob)

輪郭の検出

import cv2
import matplotlib.pyplot as plt
%matplotlib inline

img = cv2.cvtColor(cv2.imread('blob.png'), cv2.COLOR_BGR2RGB)
img_g = cv2.imread('blob.png', 0)

ret, img_bi = cv2.threshold(img_g, 100, 255, cv2.THRESH_BINARY)

img_con, countours, hierarchy = cv2.findContours(img_bi, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
img_contour = cv2.drawContours(img, countours,  -1, (255, 0, 0), 5)

fig = plt.figure(figsize=(15, 9))
plt.imshow(img_contour)