画像を二値化処理させる。さらに、二領域間の最短距離を算出できるようにした(ver1.0)。

#はじめに


※修正verを作成しました。
https://qiita.com/Fumio-eisan/items/10c54af7a925b403f59f

　製造業にて業務をしている私ですが、積極的にIoT, AI技術を導入したい会社や業界の流れを感じています。それら技術を主に取り扱う担当者は基本的に、設備技術やシステム部門になります。私自身は、製造部門に近い立場で仕事をしていてなかなか取り扱うことが少ない状況です。しかし、私自身もデジタルの流れを肌で感じいきたい！と思っています。
　今回、比較的取り扱いやすいツールとしてエッジコンピュータであるRaspberry Piを用いて遊んでみました。

　すでに様々なところで応用されていますが、カメラ撮影＋画像処理をリアルタイムで行う機能を持つセンサーの安価品として行えないか考えています。
　導入するイメージは下記です。
　![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567823/42a4931c-1703-5c48-4ef9-a8b0800537f7.png)

　手順としては以下となります。

1. 画像処理をするプログラムを作成（画像ver.）
2. 画像処理をするプログラムを作成（動画ver.）
3. Raspberry Piに環境構築＋プログラムを格納
4. 撮影した画像でリアルタイムで処理できるか確認
5. 工場の改善に繋がる良い処理方法の探索
6. KPI値を改善して、成果



　手始めに、今回は1.画像を処理するプログラムの中身を作りましたので、それをまとめたいと思います。

　この記事の要点は下記です。

+ Python/OpenCVによる二値化画像処理
+ 二値化した領域を輪郭処理
+ 領域の最短距離に線を引く、距離を求める


##使用したライブラリ及びバージョン

+ Python 3.7.4
+ numpy 1.16.5
+ Opencv-contrib-python 4.2.0.32
+ matplotlib 3.1.1


#Python/OpenCVによる二値化画像処理

　今回処理を行うサンプル画像はこちらです。

```Python:img.ipynb
import cv2
import matplotlib.pyplot as plt

img = cv2.imread('IMG.JPG')
fig = plt.figure(figsize=(8,8))
ax = fig.add_subplot(1,1,1)
ax.imshow(img)
ax.axis("off")

```
![010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567823/7d4c7c24-2294-87a8-3352-7398fdfbea11.png)



　
　流石に実際の仕事で処理したい画像を扱うことはできませんので、以前購入したサプリ画像を用います。今回は、こちらを二値化処理して大きい二つの領域を作ってそこの距離を求めたいと思います。

##threshold(閾値）による二値化処理影響
　
　今回は、二値化によって画像を処理します。二値化処理とは、カラー画像についてある画素値閾値を超えたものは白、閾値より低いものは黒へ変換して白黒画像にすることです。いわゆるモノクロ加工との違いは、モノクロ画像は白黒の間の灰色も許していることです。

```Python:img.ipynb
img = cv2.imread("IMG.jpg", 0)


threshold = 10　# 閾値の設定

ret, img_thresh = cv2.threshold(img, threshold, 255, cv2.THRESH_BINARY)# 二値化(閾値超えは255,以下は0)
fig = plt.figure(figsize=(8,8))
ax = fig.add_subplot(1,1,1)
ax.imshow(img_thresh)
ax.axis("off")

```

このthreshold値（閾値）を変えることで、白黒へ変換する画素値の閾値を変えることができます。その閾値を変えた結果がこちらです。
![003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567823/155cda83-2c59-9dd0-fef9-fde665be7281.png)

　元々の画像において白色が強いところ（≒画素値が高い）は、閾値を上げていっても最後まで元の画像として残っていることが分かります。逆に、元々黒色が強いところ（≒画素値が低い）は、閾値を下げていっても最後まで元の画像としてのころことが分かります。
　分けたい画像の分類を行う際はこの手法をよく検討する必要があります。今回は閾値100として以後の画像を処理していきます。
　他にもＲＧＢ値を調整して二値化したいところだけを上手く特徴づける手法があります。


こちらを参考にしました。
http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_thresholding/py_thresholding.html



#輪郭を抽出する

 二値化した画像の境界（≒輪郭）を抽出することがcv2.findContours()メソッドによりできます。抽出した境界の座標はcontoursのarrayに入ります。このcontours内のarrayが若干複雑でしたので、下記にまとめます。


```Python:img.ipynb

img1 = cv2.imread("IMG_re.jpg")#二値化したい画像を読み込む
gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)

contours, hierarchy = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon

def draw_contours(ax, img, contours):
    ax.imshow(img)  # 画像を表示する。
    ax.set_axis_off()
    
    for i, cnt in enumerate(contours):
        cnt = cnt.squeeze(axis=1)
        ax.add_patch(Polygon(cnt, color="b", fill=None, lw=2))
        ax.plot(cnt[:, 0], cnt[:, 1], "ro", mew=0, ms=4)
        ax.text(cnt[0][0], cnt[0][1], i, color="orange", size="10")
        

fig, ax = plt.subplots(figsize=(8, 8))
draw_contours(ax, img1, contours)

plt.show()
```

得られた画像をこちらに示します。

![005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567823/b7738bb8-8a62-84aa-dd73-3c2e67a1b4a1.png)

境界が多かったために、数字が多くて見づらい結果となりました。
この時、境界ごとにarrayに格納されています。下記に示すソートを行うことによって面積順に境界のarrayを分けることが可能となります。


```Python:img.ipynb
contours.sort(key=lambda x: cv2.contourArea(x), reverse=True)
target_contour = max(contours, key=lambda x: cv2.contourArea(x))

fig, ax = plt.subplots(figsize=(8, 8))
draw_contours(ax, img1, [target_contour])
```

![006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567823/e5381615-1599-5cea-010c-0a82a076a28b.png)

このcontoursに格納されているlistは若干最初分かりにくかったところがあるため、私自身の理解を含めて下記の画像にまとめました。
![004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567823/8e420296-b56a-eaf3-6eec-3dc76a6a9a2f.png)


##距離を求める関数を定義する。

　次に、二点間の距離を求める関数を定義します。distanceメソッドと呼ばれるものも用意されていますが、今回は自作しました。単純にx,y座標における二点のx,y座標それぞれ差の二乗を足した平方根としています。

```Python:img.ipynb

import math
def get_distance(x1, y1, x2, y2):
    d = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
    return d

```

##領域間の最短距離を求める

　次に、領域間の最短距離を求めるプログラムを書きます。今回は、一番大きい領域と二番目に大きい領域の最短距離を求めます。概要としては下記図の距離を求めることになります。
　![007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567823/b8236eea-1691-547e-92cd-b3f8cdd92673.png)
　つまり、contours[0]のx座標が最も小さい位置の点と、contours[1]のx座標が最も大きい位置の点の距離を求めることになります。
　直接この座標を読めればよかったのですが、一旦このインデックス値をargmax,argminで読み込み、そのインデックス値のｘ、ｙ座標をそれぞれ変数で与えて距離を求めています。

##最大値、最小値のインデックスを求める。
```Python:img.ipynb

import numpy as np
aa=np.argmin(contours[0][0:len(contours[0])],axis=0)
bb=np.argmax(contours[1][0:len(contours[1])],axis=0)

```

#領域間の最短距離を求め、線を引く

```Python:img.ipynb
x3 = contours[0][aa[0][0]][0][0]
y3 = contours[0][aa[0][0]][0][1]
x4 = contours[1][bb[0][0]][0][0]
y4 = contours[1][bb[0][0]][0][1]

fig, ax = plt.subplots(figsize=(8, 8))
cv2.line(img1, (x3, y3), (x4, y4), (25, 255, 255), thickness=10, lineType=cv2.LINE_4)
draw_contours(ax, img1, [contours[0]])
draw_contours(ax, img1, [contours[1]])
```

無事に最短距離に線を引くことができました。

![008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567823/c7ffde17-355f-dace-0592-ccd8b659435f.png)




#終わりに
　二値化処理を行うところまでは、すんなり処理できました。しかし、領域のある点を抽出するために、contoursのarrayを取り扱うことに若干苦労しました。
　次はこの処理を動画内でリアルタイムで処理＋表示させたいと思います。


プログラム全文はこちらに格納しています。
https://github.com/Fumio-eisan/img_distance20200315