画像解析実践入門_指紋の稜線分析 3(稜線間隔と方位のヒストグラム可視化)

https://qiita.com/kentarou524/items/7d26d6a1230fbfbb27ab

の続きです。

# 概要
指紋の稜線を分析します。前回の記事で稜線のラベリングまでしました。
最終的にはこの二値指紋画像をサブ領域に分割して、そこから特徴量(今回は稜線角度)を抽出。
指紋認識が可能か検討してみます。

#もくじ
- 1.ラベリング画像から獲得したい情報
- 2.稜線間隔と方位情報取得のアルゴリズム
- 3.アルゴリズムの関数化
- 4.ヒストグラムにて結果の確認

# 1.ラベリング画像から獲得したい情報

前回の記事でサブ領域に分割した指紋の稜線ラベル画像を作成しました。
そこら
 
- 1.稜線同士の間隔
- 2.サブ領域の稜線方位

を調べます。つまり下の画像でいうと

- <font color="Red">赤矢印</font>が**稜線同士の間隔** 
- <font color="DarkOrange">オレンジ矢印</font>が**サブ領域の稜線方位**になります。

![fing_labeld.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/644898/197983dd-6fa3-2ff8-b1aa-7658aa1ea954.png)
これをサブ領域毎に取得するアルゴリズムを考えましょう。

# 2.稜線間隔と方位情報取得のアルゴリズム

稜線方位と稜線間隔を取得するアルゴリズムを下のフローチャートにまとめました
![Untitled Diagram (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/644898/e7ec9eb9-5c92-7846-231a-333ae24c5010.png)

上図のアルゴリズムは主にループA とループB の２つに分かれていいます
#3.アルゴリズムの関数化
これを関数化したのが下記のコードになります。

```loopA.py

# ラスタスキャン
def scan_img(labeld_imgs):
  """この関数はラベリング画像をラスタスキャンして、ラベリングされた画素を見つけます。
      ラベリング画素にヒットすると、ヒットした画素とは異なるラベル画素までの最小距離と角度を取得します。
  Args:
      labeld_imgs (np.array): [ラベリングした２次元配列]

  Returns:
      distance_list(list):[最小距離リスト]
      deg_list (list): [角度リスト]

  """  
  distance_list = []
  deg_list = []
  
  #もしもラベル数が１以下の場合は解析不能として角度0とする
  if np.max(labeld_imgs)  <= 1:
    distance_list, deg_list = [0],[0]

  else:
          #ラスタスキャン開始
          h_crop,w_crop = labeld_imgs.shape
          for h0 in range(h_crop):
              for w0 in range(w_crop):
                #背景ではないラベリングされた画素にヒットした場合
                  if labeld_imgs[h0,w0] != 0:
                    #ヒットした画素とは異なるラベルの最小距離とインデックスを取得
                    min_dis,min_index = get_distance(labeld_imgs,h0,w0,h_crop,w_crop)
                    
                    #ゼロ割を回避
                    if min_index[0]-h0 == 0:
                      #90度とする。h=0(ヒット画素と垂直,同列)のため
                      deg = np.rad2deg(np.arctan(np.inf)) 

                    #最小距離との角度を計算する(-90度<deg<90度の値を取る)
                    else:  deg = np.rad2deg(np.arctan((min_index[1]-w0)/(min_index[0]-h0)))

                    distance_list.append(min_dis)
                    deg_list.append(deg)
            
  return distance_list, deg_list

```


```loopB.py
def get_distance(scan_img,h0,w0,h_crop_size,w_crop_size):
    """ラスタスキャン中にラベリングされた画素を見つけると実行される。
    ヒットした画素とは異なるラベルまでの最小距離とそのインデックスを返します。

    Args:
        scan_img [np.array]:スキャン中のラベリング画像
        h0 [int]: ヒットしたラベリング画素のインデックスh
        w0 [int]: ヒットしたラベリング画素のインデックスw
        h_crop_size [int]: クロップ画像の高さ
        w_crop_size [int]: クロップ画像の横幅

    Returns:
        min_dis[float]: ヒットした画素から異なるラベル画素までの最小距離
        min_index[tuple]: 最小距離のインデックス(h,w)
    """    
    distance_list=[]
    index_list = []
    
    #ラスタスキャン
    for h in range(h_crop_size):
        for w in range(w_crop_size):
            #背景画素ではなく、同じラベル画素ではない場合
            if scan_img[h,w] != 0 and scan_img[h,w] != scan_img[h0,w0]:
                
                #距離を計算,そのインデックスをリストに保存
                distance_list.append(np.sqrt(((h-h0)**2 + (w-w0)**2)))
                index_list.append( (h,w) )
    
    #取得した距離の中で最小値を取得
    min_dis=np.min(distance_list)
    #その最小距離のインデックスを取得
    min_index = index_list[distance_list.index(min_dis)]
    return min_dis,min_index
    

```
できました。長くなってしまったので細かい説明はまた別の記事にまとめる予定です。
とりあえず関数を実行してみましょう。

```distance_func.py
#一つのクロップ画像に関数を適応してみる
distance_list,deg_list = scan_img(labeld_imgs[1])
print(len(distance_list))#48 ラベル数と一致
print(len(deg_list))#48 ラベル数と一致


plt.figure(figsize=(10,5))
#適応したラベル画像を表示
ax = plt.subplot(1,3,1)
plt.imshow(labeld_imgs[1])

#最小距離のリストをヒストグラム表示
ax = plt.subplot(1,3,2)
_=plt.hist(distance_list,bins=25,range=(0,25))
plt.title('distance')
plt.grid()

#最小距離までの角度リストをヒストグラム表示
ax = plt.subplot(1,3,3)
_=plt.hist(deg_list,bins=25,range=(0,25))
plt.title('deg')
plt.grid()

```

![2output.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/644898/a20aa989-ba3a-25dd-d8ac-3f71f06dc1aa.png)

左から関数を適応した画像,稜線間隔のヒストグラム,角度ヒストグラムになります。
因みにラベル数は48なので稜線間隔はほとんど3~5に収まり、角度はおよそ30~40度ぐらいが多そうですね。
ではこの関数を指紋画像全てで実行してみましょう。

```scan.py
#analysis_result[クロップした領域のインデックス0~24][0->距離リスト,1->角度リスト]
analysis_result = [scan_img(labeld_imgs[i]) for i in range(len(labeld_imgs))]
```
これで結果をリストとして保存できました。

# 4.ヒストグラムにて結果の確認
ヒストグラムで結果を確認しましょう。

```hist.py
#分割したエリアごとの稜線距離ヒストグラム
plt.figure(figsize=(20,20))
for i in range(len(labeld_imgs)):
    ax = plt.subplot(crop_num,crop_num,i+1)
    _=plt.hist(analysis_result[i][0],bins=25,range=(0,25))
    plt.grid()

#分割したエリアごとの稜線方位角ヒストグラム
plt.figure(figsize=(20,20))
for i in range(len(labeld_imgs)):
    ax = plt.subplot(crop_num,crop_num,i+1)
    _=plt.hist(analysis_result[i][1],bins=(180),range=(-90,90),ec="b")
    plt.grid()
```
### 元画像
![labeld.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/644898/aba4a591-9f48-09fb-fa78-f59ebb686af7.png)

### 距離ヒストグラム
![dis_hist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/644898/35073e4d-936b-b17e-d119-323faad0e346.png)

### 稜線方位角ヒストグラム
![deg_hist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/644898/c60008e5-a15c-705d-adcb-73fd9afb54cc.png)

できました！次回はこの結果と入力画像を比較していきます。

# まとめ
ラベリング画像から稜線の間隔と方位角をアルゴリズムに従って関数を作成し
結果をヒストグラムで可視化しました。
次回はヒストグラムの結果を元画像と比較して本当に正しいか検証してみます。
続く