JIS G5502球状黒鉛鋳鉄品の黒鉛球状化率の測定プログラムの解説

# はじめに
表題のプログラムについては

https://github.com/repositoryfiles/Nodularity

にアップしておりますが、ご自分でプログラムを作ってみたい方向けに、プログラムを簡単に解説することにしました。黒色背景の部分をコピペすれば動くと思います。素人作成のプログラムのため改良の余地が多々あると思いますが、ご参考になれば幸いです。

# プログラムの解説
上記のアドレスにアップしているnodularity.pyを少し簡略化して、ファイルを一つだけ選択して球状化率を求めるプログラムについて解説します。

はじめに、必要なライブラリをインポートします。
```python:
import tkinter
from tkinter import filedialog
import math
import cv2
import matplotlib.pyplot as plt
```
<br>

続いて環境設定します。
- iDirは画像ファイルを選択する際に最初にダイアログで表示させるフォルダを指定します。
- pic_widthは画像処理で用いる画像データの横幅を指定します。どのようなサイズの画像ファイルを読み込んでも、画像処理で用いる画像データはpic_widthの幅に変換されます。
- min_grainsize=0.0071は画像の幅に対する黒鉛球状化率の評価に用いる最小黒鉛の大きさを表します。例えば、画像の幅100に対して黒鉛球状化率の評価に用いる最小黒鉛の大きさが1の組織画像を読み込む場合はmin_grainsize=0.01とします。
```python:
iDir='C:/Data'
pic_width=1920
min_grainsize=0.0071
```
<br>
ダイアログを使って画像ファイルを選択します。nodularity.pyではget_picture_filenames()という関数で複数のファイルを選択していますが、ここでは関数にしないで記述しました。
<br>

```python:
root=tkinter.Tk()
root.withdraw()
fTyp = [("jpg", "*.jpg"), ("BMP", "*.bmp"), ("png", "*.png"), ("tiff", "*.tif")] 
filename = filedialog.askopenfilename(title='画像ファイルを選んでください', filetypes=fTyp, initialdir=iDir) 
```
<br>

ダイアログで選択した画像ファイルを開いて、サイズを調べます。ここでは、上記URLにアップしている「sample1(min_grainsize=0.007).jpg」を読み込んでいます。
```python:
img_color_ISO = cv2.imread(filename)
img_height, img_width, channel = img_color_ISO.shape
```
<br>

読み込んだ画像を幅pic_width（=1920）、高さpic_heightの大きさに変換し、そのコピー（img_color_ISO, img_color_JIS）を作成しておきます。
以下の画像処理は、pic_width×pic_heightの大きさに変換した画像を用いています。
```python:
pic_height=int(pic_width * img_height / img_width)
img_color_ISO = cv2.resize(img_color_ISO, (pic_width, pic_height)) 
img_color_JIS = img_color_ISO.copy() 
```
<br>

ここで、img_color_ISOを表示してみます。
```python:
plt.imshow(cv2.cvtColor(img_color_ISO, cv2.COLOR_BGR2RGB))
plt.show()
```
![e01dce27-37d8-4fdc-890f-78400ebc44f5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1729073/9f1f380c-66f1-0023-4858-e8350f21395b.png)

img_color_ISO、img_color_JISのいずれかについて、グレー変換してから白黒反転の二値化処理をします。二値化処理には、しきい値を自動的に求める大津の二値化を用いています。
```python:
img_gray = cv2.cvtColor(img_color_ISO, cv2.COLOR_BGR2GRAY)
ret, img_inv_binary = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
```
<br>

白黒反転の二値化処理後の画像を表示してみます。
```python:
plt.imshow(img_inv_binary, cmap = "gray")
plt.show()
```

![adf0477a-7ae1-4585-aa64-262ab9f8a2f3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1729073/845e7326-2389-9a31-6671-7d37fef24602.png)


二値化画像から輪郭を検出します。

```python:
contours, hierarchy = cv2.findContours(img_inv_binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) 
```
<br>

ダイアログで選択した画像ファイルを新たに開き、今ほど検出した輪郭を赤色で重ね書きしてみます。

```python:
img= cv2.imread(filename)
img = cv2.resize(img, (pic_width, pic_height)) 
cv2.drawContours(img, contours, -1, (0, 0,255), 2)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.show()
```
![97f77c68-20da-4dc1-8462-82391d628cdd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1729073/c13678ad-b71c-59f8-58e4-0ba8db036e62.png)
黒鉛球状化率の評価に用いる黒鉛を選別する関数を定義します。
この関数は、contours、画像の幅と高さ、min_grainsizeを入力して、画像の縁に接している輪郭や、min_grainsizeより小さい輪郭をcontoursから取り除いてconters1に格納するものです。
関数定義後にその関数を実行します。
```python:
def select_contours(contours, pic_width, pic_height, min_grainsize):
    contours1 = []
    for e, cnt in enumerate(contours):
        x_rect, y_rect, w_rect, h_rect = cv2.boundingRect(cnt)
        (x_circle, y_circle), radius_circle = cv2.minEnclosingCircle(cnt)
        if int(pic_width * min_grainsize) <= 2 * radius_circle \
            and 0 < int(x_rect) and 0 < int(y_rect) and \
            int(x_rect + w_rect) < pic_width and int(y_rect + h_rect) < pic_height:
            contours1.append(cnt)  
    return contours1

contours1 = select_contours(contours, pic_width, pic_height, min_grainsize) 
```
<br>

ダイアログで選択した画像ファイルを新たに開き、contours1の輪郭を赤色で重ね書きしてみます。黒鉛球状化率の評価に用いる黒鉛の輪郭だけが赤色で表示されています。

```python:
img= cv2.imread(filename)
img = cv2.resize(img, (pic_width, pic_height)) 
cv2.drawContours(img, contours1, -1, (0, 0,255), 2)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.show()
```

![dc3161c1-ea09-480c-af6f-2bcf91d73d78.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1729073/38fb6d7b-d257-9ec9-f236-1adeebd41e9c.png)

黒鉛球状化率の計算で用いる定数を初期化します。
```python:
sum_graphite_areas = 0
sum_graphite_areas_5and6 = 0
num_graphite1 = num_graphite2 = num_graphite3 = num_graphite4 = num_graphite5 = 0
```
<br>

黒鉛の最大軸を求める関数を定義します。
この関数は、輪郭の凸包を入力すると、輪郭の最大軸の中心位置（x,y）と最大軸の半分の長さ（max_distance * 0.5）を返します。
```python:
def get_graphite_length(hull):
    max_distance = 0
    for i, hull_x in enumerate(hull):
        for j, hull_y in enumerate(hull):
            if j + 1 < len(hull) and i != j + 1:
                dis_x = hull[j+1][0][0] - hull[i][0][0]
                dis_y = hull[j+1][0][1] - hull[i][0][1]
                dis = math.sqrt(dis_x**2 + dis_y**2)
                if dis > max_distance:
                    max_distance = dis
                    x = dis_x * 0.5 + hull[i][0][0]
                    y = dis_y * 0.5 + hull[i][0][1]
    return(x, y, max_distance * 0.5)
```
<br>

contours1の全ての輪郭に対して、丸み係数を用いて形状評価をして球状化率を算出します。
```python:
marumi_ratio = 0.6
for i, cnt in enumerate(contours1): 
    graphite_area = cv2.contourArea(cnt)
    sum_graphite_areas += graphite_area
    hull = cv2.convexHull(cnt) # 凸包
    x, y, graphite_radius = get_graphite_length(hull)
    marumi = graphite_area / ((graphite_radius ** 2) * math.pi)

    # ISO法による形状ⅤとⅥの黒鉛判定
    if marumi >= marumi_ratio:
        sum_graphite_areas_5and6 += graphite_area
        cv2.drawContours(img_color_ISO, contours1, i, (0, 0, 255), -1) #赤色

    # JIS法による形状分類
    if marumi <= 0.2:
        num_graphite1 += 1
        cv2.drawContours(img_color_JIS, contours1, i, (255, 255, 0), -1) #水色
    if 0.2 < marumi <= 0.4:
        num_graphite2 += 1
        cv2.drawContours(img_color_JIS, contours1, i, (0, 255, 0), -1) #緑
    if 0.4 < marumi <= 0.7:
        num_graphite3 += 1
        cv2.drawContours(img_color_JIS, contours1, i, (128, 0, 128), -1) #紫
    if 0.7 < marumi <= 0.8:
        num_graphite4 += 1
        cv2.drawContours(img_color_JIS, contours1, i, (255, 0, 0), -1) #青
    if 0.8 < marumi:
        num_graphite5 += 1
        cv2.drawContours(img_color_JIS, contours1, i, (0, 0,255), -1) #赤
                    
# 球状化率（ISO法）
nodularity_ISO = sum_graphite_areas_5and6 / sum_graphite_areas * 100
# 球状化率（JIS法）
nodularity_JIS = (0.3 * num_graphite2 + 0.7 * num_graphite3 + 0.9 * num_graphite4 + 1.0 * num_graphite5)/ len(contours1) * 100
```
<br>

算出した球状化率を表示します。
```python:
print(f'球状化率 ISO法:{nodularity_ISO:.2f}%, JIS法:{nodularity_JIS:.2f}%')
```
球状化率 ISO法:86.21%, JIS法:83.47%
<br>

黒鉛の形状分類の結果をimg_color_ISO、img_color_JISに重ね書きして表示します。
こちらはJIS法による形状分類の結果です。個々の黒鉛に対して、丸み係数0.2～0.4は緑色、0.4～0.7は紫色、0.7～0.8は青色、0.8以上は赤色で表示しています。
```python:
plt.imshow(cv2.cvtColor(img_color_JIS, cv2.COLOR_BGR2RGB))
plt.show()
```
![5810b332-1ff2-4713-8282-1cf65f858020.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1729073/8073f05d-4ea4-1f60-ff82-41e93a9b53a8.png)

こちらはISO法による形状分類の結果です。丸み係数0.6以上の黒鉛が赤色で表示されています。
```python:
plt.imshow(cv2.cvtColor(img_color_ISO, cv2.COLOR_BGR2RGB))
plt.show()
```
![8e021028-6c97-4960-b14e-8b481e68e228.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1729073/4448df5d-04fb-92a2-6996-5a74d03ed7c3.png)

なお、githubにアップしたnodularity.pyでは、黒鉛の輪郭だけに色付けしています。