Python_(3/4)複数ウェル画像を個別に切り出して画像解析してエクセル(384ウェル) | バイオとDX

バイオとDX

バイオ医薬品とDX、データサイエンス

# ===============================================
# 周囲をトリミングしたウェルをリサイズして各ウェルを切り出して解析
# ===============================================

import cv2
import numpy as np
import os
import openpyxl
import datetime

# ■■■検出する大きさの上下限をここで加減する■■■
#輪郭抽面積の判断基準 area_min: ※輪郭内の面積がこれ未満なら不採用
#輪郭抽面積の判断基準 area_max: ※輪郭内の面積がこれ超過なら不採用
area_min = 1000
area_max = 3000

# ■■■ここに解析したいプレート画像を列記する■■■
# あらかじめ画像はウェルだけにトリミングしておく
#画像ファイル名入力
#fname = input('input file name to be read ,as ###.jpg:')
photo = [
    'plt1',
    'plt2',
    'plt3',
    'plt4',
    'plt5',
    'plt6',
]

#日付取得/ファイル名のため
d_today1 = datetime.date.today()
d_today = str(datetime.date.today())

#ウェルの番地準備/ファイル名のため/文字表記
y_letter = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P']
x_letter = ['01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24']

# xyの空リスト用意
xy_list = []
area_list = []

# ■■■ここから画像分繰り返し■■■
for j, photo1 in enumerate(photo):
    photo1 = str(photo[j]) + '.jpg'

# ワークブック作成とシート指定
    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = str(photo[j])

#画像読み込み
    img = cv2.imread(photo1)

    if img is None:
        print ('ファイルを読み込めません。')
        import sys
        sys.exit()

#画像リサイズ
    height = img.shape[0]
    width = img.shape[1]

    dst = cv2.resize(img, (960, 640))

# ===============================================
# 384ウェルのウェル1つずつ抽出、保存
# ===============================================

#(個別)画像ファイル保存フォルダ作成
# OpenCVのimwriteで保存先ディレクトリを指定
# https://jd-engineer.hateblo.jp/entry/2017/02/24/202747

    dirname = d_today+'_'+photo[j]
    if not os.path.exists(dirname):
        os.mkdir(dirname)

    for y1 in range(0, 16, 1):
        for x1 in range(0, 24, 1):

#ウェルの番地設定/IDの文字列化/アドレスのためx40
            y2 = y1*40
            x2 = x1*40

#保存した(個別)画像ファイルの特定
            dst2 = dst[y2:y2+40, x2:x2+40]

#(個別)画像ファイルの保存
# OpenCVのimwriteで保存先ディレクトリを指定
# https://jd-engineer.hateblo.jp/entry/2017/02/24/202747
            name1 = d_today+'_'+photo1[j]+'_'+y_letter[y1]+x_letter[x1]
            name2 = y_letter[y1]+x_letter[x1]
        
#画像書き込み0
#        cv2.imwrite(os.path.join(dirname, f'({name1}.jpeg'), dst2)

# ===============================================
# 個別画像をトリミング
# ===============================================

#画像リサイズ/#120x120でリサイズ
            height = dst2.shape[0]
            width = dst2.shape[1]
            dst3 = cv2.resize(dst2, (120, 120))

#画像書き込み1
#          cv2.imwrite(os.path.join(dirname, f'({name1}_120.jpeg'), dst3)

#上下左右トリミング/#15-105x15-105で上下左右トリミング
            dst4 = dst3[15:105, 15:105]

#画像書き込み2
#          cv2.imwrite(os.path.join(dirname, f'({name1}_90.jpeg'), dst4)

#四隅トリミング/中心45:45で横100縦130の円、(255,255,0)は線の色、thickness-1で塗りつぶし円でマスク
            mask = np.zeros(dst4.shape, dtype=np.uint8)
            cv2.ellipse(mask,((45, 45),(100, 130),0),(255,255,0),-1)
            dst5 = dst4 & mask

#画像書き込み3
#         cv2.imwrite(os.path.join(dirname, f'({name1}_90_mask.jpeg'), dst5)

# ===============================================
# ノイズ除去/GaussianBlur
# ===============================================

#画像img_gray#(2)GaussianBlurを用いたノイズ除去
#第二引数pixelは平均化するピクセル数(奇数で)で、1x1の場合は計1ピクセル
            pixel = 1
            img_gaus = cv2.GaussianBlur(dst5,(pixel,pixel),2)

# ===============================================
# 二値化
# ===============================================

#二値化する
#二値化する際の輝度閾値
            thresh_value=120
            thresh, dst6 = cv2.threshold(img_gaus, thresh_value, 255, cv2.THRESH_BINARY)

#画像書き込み4
#          cv2.imwrite(os.path.join(dirname, f'({name1}_gaus.jpeg'), dst6)

# ===============================================
# 輪郭を抽出して面積を計算
# ===============================================

#グレイスケール化して二値化
            dst7 = cv2.cvtColor(dst6,cv2.COLOR_BGR2GRAY)
            ret,thresh = cv2.threshold(dst7,120,255,0)
        
#輪郭を抽出 ※輪郭を検出する順番は、輪郭抽出の開始点の座標のyの値が大きい順に検出される
# https://imagingsolution.net/program/python/opencv-python/opencv-python-findcontours/
# https://qiita.com/bianca26neve/items/9f15e2a7856da01592b9
# https://pystyle.info/opencv-structural-analysis-and-shape-descriptors/
            contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) 

#輪郭抽出ノイズの判断基準 contour_noise:3 ※輪郭この数値以上のみリスト化
            contour_noise =3

#面積が閾値未満の輪郭の削除する
            contours2 = list(filter(lambda x: cv2.contourArea(x) >= contour_noise, contours))

# 一番面積が大きい輪郭を選択/輪郭の数が1の時だけ採用、1以外(ゼロか複数)なら不採用
            if(len(contours2) == 1):
                cnt = max(contours2, key=lambda x: cv2.contourArea(x))

#輪郭の面積を計算/findContours() で抽出された輪郭から cv2.contourArea で
                area = cv2.contourArea(cnt)
                if area > area_min and area < area_max:

# 適合するxyをリストに追加 ←For分の中にいれてxyリストを完成させる
                    xy_list.append(name2)
                    area_list.append(area)

#画像書き込み5
                    cv2.imwrite(os.path.join(dirname, f'{area:05.0f}_{name1}.jpeg'), dst2)
                    cv2.imwrite(os.path.join(dirname, f'{area:05.0f}_{name1}_bn.jpeg'), thresh)

# xyのリスト分繰り返して「A2」からエクセルに入力 ←xy_listさえできてれば最後でOK
    for i, xy in enumerate(xy_list):
        ws.cell(1,1).value = 'plate ID'
        ws.cell(1,2).value = 'well address'
        ws.cell(1,3).value = 'area'
        ws.cell(i+2,1).value = photo[j]
        ws.cell(i+2,2).value = xy_list[i]
        ws.cell(i+2,3).value = area_list[i]

# エクセルの保管
    wb.save(f'{dirname}.xlsx')

# ■■■ここまで画像分繰り返し■■■

#cv2終了作業
cv2.waitKey(0)
cv2.destroyAllWindows()

# エクセルの終了
wb.close