インスタンスセグメンテーションした粒子画像から特徴量を計算する 面積・粒子径

OpenCV

前回はBPartISモデル利用して、粒子画像のインスタンスセグメンテーションを実行した。

続いて今回は、セグメンテーション画像から各粒子の特徴量として以下を計算する。
・面積
・周囲長
・最小外接短径の短辺、長辺、傾き
・楕円近似の短軸、長軸、傾き
・最小外接円の半径

各種特徴量の計算にはopencvライブラリを使用しており、下記のページを参考にしている。

輪郭: 初めの一歩 — OpenCV-Python Tutorials 1 documentation
領域(輪郭)の特徴 — OpenCV-Python Tutorials 1 documentation

ディレクトリ構成

ディレクトリ構成は以下の通り。この記事で新規に作成したものはmeasurement.pyのみで、コードの詳細はページ下部にまとめた。

.
└── bpartis
    ├── models
    |   └seg-model.pt
    └── segment
        ├── model.py
        ├── nnmodules.py
        ├── cluster.py
        ├── uncertainty.py
        ├── visualization.py
        └── measurement.py

その他のコードは、下記記事にまとめている。

実際に動かしてみた

ライブラリをインポート。

import cv2
import matplotlib.pyplot as plt

from bpartis.segment.measurement import Measurer

  

テスト用画像(test2imageモデルで生成した画像)、セグメンテーション画像の読み込み。

image = cv2.imread('./test.png')
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
plt.show()

segmentation = cv2.imread('./results/test_seg.png', cv2.IMREAD_GRAYSCALE)
plt.imshow(segmentation)
plt.show()

ここで、セグメンテーション画像は2次元のndarrayで、背景は0、各粒子の画素にはインスタンスのidが画素値となっている。

  

特徴量計算用のクラスで、特徴量を算出して表示。

measurer = Measurer(segmentation, image)
print(measurer.params)

ここで、特徴量名と詳細は以下の通り。

特徴量名詳細特徴量名詳細
area面積ellipse_short楕円近似の短軸
perimeter周囲長ellipse_long楕円近似の長軸
rect_short最小外接短形の短辺ellipse_angle楕円近似の傾き
rect_long最小外接短形の長辺circle_r最小外接円の半径
rect_angle最小外接短形の傾き

  

粒子の輪郭を図示する。

plt.imshow(measurer.draw_contours())
plt.show()

  

さらに粒子番号を画像に付番する。

img = measurer.draw_contours()
plt.imshow(measurer.draw_num(img))
plt.show()

  

その他も同様に図示できる。

# 最小外接短形
plt.imshow(measurer.draw_rect())
plt.show()

# 最小外接円
plt.imshow(measurer.draw_circle())
plt.show()

# 楕円近似
plt.imshow(measurer.draw_ellipse())
plt.show()
最小外接短形
最小外接円
楕円近似

  

コード詳細

githubはこちら

GitHub - shashashanki/bpartis_test
Contribute to shashashanki/bpartis_test development by creating an account on GitHub.
import numpy as np
import pandas as pd
import cv2


class Measurer:
    def __init__(self, segmentation, image=[]):
        self.segmentation = segmentation
        if image!=[]:
            self.image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        else:
            self.image = image
        # unit8に変換しないとcv2で処理できない
        self.contours, self.hierrarchy = cv2.findContours(segmentation.astype('uint8'),cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
        
        self.rects = []
        self.circles = []
        self.ellipses = []
        self.params = pd.DataFrame(self.calc_param())
        self.params.index += 1


    def calc_param(self):
        param_li = []
        
        for cnt in self.contours:
            # 面積
            area = cv2.contourArea(cnt)
            # 周囲長
            perimeter = cv2.arcLength(cnt,True)
            # 最小外接短形
            rect = cv2.minAreaRect(cnt)
            rect_l = [rect[1][0],rect[1][1]]
            rect_l.sort()
            # 最小外接円
            circle = cv2.minEnclosingCircle(cnt)
            # 楕円近似
            ellipse = cv2.fitEllipse(cnt)
            ellipse_r = [ellipse[1][0],ellipse[1][1]]
            ellipse_r.sort()

            param = {
                    'area':area,
                    'perimeter':perimeter,
                    'rect_short':rect_l[0],
                    'rect_long':rect_l[1],
                    'rect_angle':rect[2],
                    'ellipse_short':ellipse_r[0],
                    'ellipse_long':ellipse_r[1],
                    'ellipse_angle':ellipse[2],
                    'circle_r':circle[1],
            }
            param_li.append(param)
            self.rects.append(rect)
            self.circles.append(circle)
            self.ellipses.append(ellipse)
        
        return param_li

    def draw_contours(self):
        img = self.image.copy()
        img = cv2.drawContours(img, self.contours, -1, (0,255,0), 2)
        return img

    def draw_rect(self):
        img = self.image.copy()
        for rect in self.rects:
            box = cv2.boxPoints(rect)
            box = np.int0(box)
            img = cv2.drawContours(img,[box],0,(0,255,0),2)
        return img

    def draw_circle(self):
        img = self.image.copy()
        for circle in self.circles:
            (x,y),radius = circle
            center = (int(x),int(y))
            radius = int(radius)
            img = cv2.circle(img,center,radius,(0,255,0),2)
        return img
    
    def draw_ellipse(self):
        img = self.image.copy()
        for ellipse in self.ellipses:
            img = cv2.ellipse(img,ellipse,(0,255,0),2)
        return img

    def draw_num(self, img_):
        img = img_.copy()
        for i,rect in enumerate(self.rects):
            x,y = int(rect[0][0]),int(rect[0][1])
            img = cv2.putText(
                            img,
                            text=f'{i+1}',
                            org=(x,y),
                            fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                            fontScale=1.0,
                            color=(0,0,255),
                            thickness=2,
                            )
        return img

コメント

タイトルとURLをコピーしました