Python 図形の境界を検出して、各領域の面積を求める「OpenCV」

 本記事では、画像認識により図形の輪郭を検出して、各領域の面積を求める雛形コードを載せました。同時に、各領域の重心座標と輪郭の長さも数値データとしてcsvファイルに保存し、ヒストグラムや累積分布図も作成する仕様です。
 各領域の検出と面積を求める過程を動画にしたのが下記リンク先です。

www.youtube.com

下図は、元画像です。
本プログラム中では画像ファイル名「'yyy_picture.jpg'」で指定しています。

f:id:HK29:20210411080432j:plain

下図は、本プログラム実行後の図です。
検出した領域を赤線で囲み、面積を各領域の重心位置に記載しています。

f:id:HK29:20210411080410j:plain

 本プログラムを実行して得られた数値データは、下図のようにcsvファイルに保存します。B列のIDは領域名、C列のAreaは面積、D列とE列は領域の重心(x,y)、F列は輪郭の長さ、Gは画像面積全体を100%とした場合の各領域が占める割合を表します。

f:id:HK29:20210411080738p:plain

下図のようにヒストグラムを作成します。

f:id:HK29:20210411080604j:plain

下図のように累積分布図も作成します。

f:id:HK29:20210411080626j:plain

■本プログラム

#!/usr/bin/env python
# coding: utf-8

# In[1]:


import os
import cv2
import numpy as np
import scipy.ndimage as ndimage
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
plt.rcParams["font.size"] = 20
import pandas as pd

file_path = 'yyy_picture.jpg'
basename = os.path.splitext(os.path.basename(file_path))[0]


# In[2]:


### 画像読み込み
img = cv2.imread(file_path, 1)
# 画像の高さと幅を取得
h, w, c = img.shape
# 拡大(拡大することで輪郭がぼやける。このぼやけにより境界を識別しやすくする)
scale = 3
img_resize = cv2.resize(img, (w * scale, h * scale))

### 画像処理
# グレースケールに変換
img_gray = cv2.cvtColor(img_resize, cv2.COLOR_BGR2GRAY)

# ガウシアンによるスムージング処理(ぼかし)
img_blur = cv2.GaussianBlur(img_gray, (5,5), 0)

# 二値化と大津処理
r, dst = cv2.threshold(img_blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

# モルフォロジー膨張処理
kernel = np.ones((3,3), np.uint8)
dst = cv2.dilate(dst, kernel, iterations = 1)

# 画像ファイルに保存
cv2.imwrite(basename + '_thresholds.jpg', dst)


# In[3]:


# もし画像欠けがあった場合に塗りつぶす処理
dst_fill = ndimage.binary_fill_holes(dst).astype(int) * 255
cv2.imwrite(basename + '_thresholds_fill.jpg', dst_fill)


# In[4]:


# 境界を検出して描画する
contours, _ = cv2.findContours(dst_fill.astype(np.uint8), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
img_contour = cv2.drawContours(img_resize, contours, -1, (0,0,255), 1)
cv2.imwrite(basename + '_counter.jpg', img_contour)


# In[5]:


# 面積、重心、輪郭長さを抽出する
Areas = []
with open(basename + '_data.csv', 'w') as f:
    for i, contour in enumerate(contours):
        # 面積
        area = cv2.contourArea(contour)
        area = area / 1000
        Areas.append(area)

        # 輪郭の重心
        M = cv2.moments(contour)
        cx = int(M["m10"] / M["m00"])
        cy = int(M["m01"] / M["m00"])  

        #輪郭(境界)の長さ
        perimeter = cv2.arcLength(contours[i],True)        
        
        # 画像に出力する
        #img2 = img_resize.copy()
        img2 = cv2.drawContours(img_resize, contours, i, (0, 0, 255), 3)
        cv2.putText(img2, str('{:.1f}'.format(area)), (cx, cy),
                    cv2.FONT_HERSHEY_PLAIN, # フォントタイプ
                    3, # 文字サイズ
                    (0, 0, 0), # 文字色:白(255, 255, 255) 黒(0, 0, 0)
                    2, # 文字太さ
                    cv2.LINE_AA)

        if i == (len(contours)-1):
            img2_resize = cv2.resize(img2, (w, h))
            cv2.imwrite(basename + '_' + str(i) + '.jpg', img2_resize)

        # csvファイルに保存
        if i == 0:
            my_columns_list = ['ID', 'Area', 'x_center_of_gravity', 'y_center_of_gravity', 'Perimeter']
            my_columns_str = ','.join(my_columns_list)
            f.write(my_columns_str + '\n')
        else:
            my_data_list = [str(i), str(area), str(cx), str(cy), str(perimeter)]
            my_data_str = ','.join(my_data_list)
            f.write(my_data_str + '\n')
    
Area_sum = sum(Areas)
print('Area_sum', Area_sum)


# In[6]:


# ヒストグラム表示
fig = plt.figure(figsize=(8,6))
plt.title("histogram")
plt.xlabel('Area')
plt.ylabel('frequency')
plt.tick_params()
plt.grid()
plt.hist(Areas, bins=10, rwidth=0.9) # binsは区分数
plt.savefig(basename + '_histogram.jpg')
plt.close()


# In[7]:


# 面積を割合で出力する
df = pd.read_csv(basename + '_data.csv')
df[r'Area[%]'] = df['Area'] / Area_sum * 100
df.to_csv(basename + '_data_2.csv')
df


# In[8]:


### 累積分布図を表示する
# ヒストグラムデータを抽出
values, base = np.histogram(df['Area'], bins=40) # binsは区分数

# 要素を足し合わせて、numpyアレイで出力する
y = np.cumsum(values)

# グラフをプロット
fig = plt.figure(figsize=(8,6))
plt.plot(base[:-1], y, color='black', linewidth = 4)

# 以下、グラフオプション
plt.xlabel('Area')
plt.ylabel('p')
#plt.ylim(0, 1)
# 目盛り表記を強制的に整数にする
plt.gca().get_yaxis().set_major_locator(ticker.MaxNLocator(integer=True))
plt.grid()
plt.title('Cumulative distribution')
plt.savefig(basename + '_cumulative_distribution.jpg')
plt.close()


# In[ ]:

以上

<広告>