PythonMania

普段はロボットとAIを組み合わせて色々作ってます。Python関係以外も色々投稿していくと思います。

【Python】画像認識 - 画像の前加工とImageDataBunch(faceai用データ形式)の作成【DeepLearning】

今回は以下のコンペで画像認識の勉強をしてみました。


www.kaggle.com





kaggleでよく見かける「faceai」というフレームワークの学習用データ形式を作成するところまで記載してあります。



また今回はデータの前処理で画像のトリミング、及び極端にピクセル値が偏っている(真っ暗or真っ白)の画像を

検出する処理も行っています。



以下コードです。






#---------------------------------------------------------------------------------------------------------------
#必要なライブラリのインポート
import numpy as np
import pandas as pd
import os
import cv2
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import random
from sklearn.utils import shuffle
from tqdm import tqdm_notebook


#データの読み込み
data = pd.read_csv('/kaggle/input/train_labels.csv')
train_path = '/kaggle/input/train/'
test_path = '/kaggle/input/test/'
#ポジティブ・ネガティブ間のデータ数の確認
data['label'].value_counts()


#---------------------------------------------------------------------------------------------------------------
#データの前加工
#がんの有無の特徴は画像の中央部に表示されるため、画像の中央部分をトリミング
#その他画像複製するためのパラメータをランダムに設定する

import random
ORIGINAL_SIZE = 96      # オリジナル画像のサイズ これは変更しない

#画像を加工・複製する際のパラメータ
CROP_SIZE = 90          #トリミング後の画像サイズ
RANDOM_ROTATION = 3    #画像を回転
RANDOM_SHIFT = 2        #トリミング後の画像をx,y方向にずらすこれは (ORIGINAL_SIZE - CROP_SIZE)//2 を超えてはならない
RANDOM_BRIGHTNESS = 7  # 明るさ (0-100), 0=no change
RANDOM_CONTRAST = 5    # コントラスト (0-100), 0=no change
RANDOM_90_DEG_TURN = 1


#加工した画像の読み込み
def readCroppedImage(path , augmentations = True):
    
    #OpenCVで画像をbgr読み込み
    bgr_img = cv2.imread(path)
    #画像をb,g,rに分離して読み込み
    b,g,r = cv2.split(bgr_img)
    #RGB画像も作成しておく
    rgb_img = cv2.merge([r,g,b])
    
    if(not augmentations):
        return rgb_img / 255
        
        
        
    #画像をランダムに回転
    rotation = random.randint(-RANDOM_ROTATION,RANDOM_ROTATION)
    f(RANDOM_90_DEG_TURN == 1):
        rotation += random.randint(-1,1) * 90
    #画像の中心を回転の軸とする
    M = cv2.getRotationMatrix2D((48,48),rotation,1) 
    #回転の適用
    rgb_img = cv2.warpAffine(rgb_img,M,(96,96))



  #画像をx,y方向にシフト
  x = random.randint(-RANDOM_SHIFT, RANDOM_SHIFT)
  y = random.randint(-RANDOM_SHIFT, RANDOM_SHIFT)
  
  
  #トリミングを行い、値を0-1で正規化
  start_crop = (ORIGINAL_SIZE - CROP_SIZE) //2
  end_crop = start_crop + CROP_SIZE
  rgb_img = rgb_img[(start_crop + x):(end_crop + x), (start_crop + y):(end_crop + y)] / 255
  
  
   flip_hor = bool(random.getrandbits(1))
    flip_ver = bool(random.getrandbits(1))
    if(flip_hor):
        rgb_img = rgb_img[:, ::-1]
    if(flip_ver):
        rgb_img = rgb_img[::-1, :]
        
    #輝度の調整
    br = random.randint(-RANDOM_BRIGHTNESS, RANDOM_BRIGHTNESS) / 100.
    rgb_img = rgb_img + br
    
    #コントラスト調整
    cr = 1.0 + random.randint(-RANDOM_CONTRAST, RANDOM_CONTRAST) / 100.
    rgb_img = rgb_img * cr
    
    #0-1の値に収める
    rgb_img = np.clip(rgb_img, 0, 1.0)
    
    return rgb_img
    
    
#数枚プロットしてみる

# random sampling

fig, ax = plt.subplots(2,5, figsize=(20,8))
fig.suptitle('Cropped histopathologic scans of lymph node sections',fontsize=20)
# Negatives
for i, idx in enumerate(shuffled_data[shuffled_data['label'] == 0]['id'][:5]):
    path = os.path.join(train_path, idx)
    ax[0,i].imshow(readCroppedImage(path + '.tif'))
ax[0,0].set_ylabel('Negative samples', size='large')
# Positives
for i, idx in enumerate(shuffled_data[shuffled_data['label'] == 1]['id'][:5]):
    path = os.path.join(train_path, idx)
    ax[1,i].imshow(readCroppedImage(path + '.tif'))
ax[1,0].set_ylabel('Tumor tissue samples', size='large')                    



#ピクセルが極端に暗いもしくは明るい画像を調べる これらは正常に動作しない可能性があるため
dark_th = 10 / 255      # 10/255以下のものは極端に暗いピクセル
bright_th = 245 / 255   #この値よりも大きなものはほぼ真っ白なピクセル
too_dark_idx = []
too_bright_idx = []

x_tot = np.zeros(3)
x2_tot = np.zeros(3)
counted_ones = 0
for i, idx in tqdm_notebook(enumerate(shuffled_data['id']), 'computing statistics...(220025 it total)'):
    path = os.path.join(train_path, idx)
    imagearray = readCroppedImage(path + '.tif', augmentations = False).reshape(-1,3)
    # 暗いもの
    if(imagearray.max() < dark_th):
        too_dark_idx.append(idx)
        continue # 
    # 極端に明るいもの
    if(imagearray.min() > bright_th):
        too_bright_idx.append(idx)
        continue # 
    x_tot += imagearray.mean(axis=0)
    x2_tot += (imagearray**2).mean(axis=0)
    counted_ones += 1
    
channel_avr = x_tot/counted_ones
channel_std = np.sqrt(x2_tot/counted_ones - channel_avr**2)
channel_avr,channel_std


print('There was {0} extremely dark image'.format(len(too_dark_idx)))
print('and {0} extremely bright images'.format(len(too_bright_idx)))
print('Dark one:')
print(too_dark_idx)
print('Bright ones:')
print(too_bright_idx)


#極端に暗いもの、明るいものをプロットしてみる
Plot some of the very bright or very dark images
fig, ax = plt.subplots(2,6, figsize=(25,9))
fig.suptitle('Almost completely black or white images',fontsize=20)
# Too dark
i = 0
for idx in np.asarray(too_dark_idx)[:min(6, len(too_dark_idx))]:
    lbl = shuffled_data[shuffled_data['id'] == idx]['label'].values[0]
    path = os.path.join(train_path, idx)
    ax[0,i].imshow(readCroppedImage(path + '.tif', augmentations = False))
    ax[0,i].set_title(idx + '\n label=' + str(lbl), fontsize = 8)
    i += 1
ax[0,0].set_ylabel('Extremely dark images', size='large')
for j in range(min(6, len(too_dark_idx)), 6):
    ax[0,j].axis('off') # hide axes if there are less than 6
# Too bright
i = 0
for idx in np.asarray(too_bright_idx)[:min(6, len(too_bright_idx))]:
    lbl = shuffled_data[shuffled_data['id'] == idx]['label'].values[0]
    path = os.path.join(train_path, idx)
    ax[1,i].imshow(readCroppedImage(path + '.tif', augmentations = False))
    ax[1,i].set_title(idx + '\n label=' + str(lbl), fontsize = 8)
    i += 1
ax[1,0].set_ylabel('Extremely bright images', size='large')
for j in range(min(6, len(too_bright_idx)), 6):
    ax[1,j].axis('off') #
    
    
#---------------------------------------------------------------------------------------------------------------
#モデル作成

from sklearn.model_selection import train_test_split

#データを先に読み込んでpd.dataframeにして、インデックスをidにする
train_df = data.set_index('id')

train_names = train_df.index.values
train_labels = np.asarray(train_df['label'].values)

#データの分割
tr_n, tr_idx, val_n, val_idx = train_test_split(train_names, range(len(train_names)), test_size=0.1, stratify=train_labels, random_state=123)


#必要なライブラリのインポート
from fastai import *
from fastai.vision import *
from torchvision.models import *

#モデルの選択
arch = densenet169
#バッチサイズ
batchsize = 128
#入力データのサイズ
sz = CROP_SIZE
MODEL_PATH = str(arch).split()[1]

#faceaiでデータを読み込むための形式、ImageDataBunchにする
train_dict = {'name': train_path + train_names, 'label': train_labels}
df = pd.DataFrame(data=train_dict)
test_names = []
for f in os.listdir(test_path):
    test_names.append(test_path + f)
df_test = pd.DataFrame(np.asarray(test_names), columns=['name'])
#
class MyImageItemList(ImageList):
    def open(self, fn:PathOrStr)->Image:
        img = readCroppedImage(fn.replace('/./','').replace('//','/'))
        return vision.Image(px=pil2tensor(img, np.float32))
    
#ImageDataBunch作成
imgDataBunch = (MyImageItemList.from_df(path='/', df=df, suffix='.tif')
        #Where to find the data?
        .split_by_idx(val_idx)
        #How to split in train/valid?
        .label_from_df(cols='label')
        #Where are the labels?
        .add_test(MyImageItemList.from_df(path='/', df=df_test))
        #dataframe pointing to the test set?
        .transform(tfms=[[],[]], size=sz)
        # We have our custom transformations implemented in the image loader but we could apply transformations also here
        # Even though we don't apply transformations here, we set two empty lists to tfms. Train and Validation augmentations
        .databunch(bs=BATCH_SIZE)
        # convert to databunch
        .normalize([tensor([0.702447, 0.546243, 0.696453]), tensor([0.238893, 0.282094, 0.216251])])

#---------------------------------------------------------------------------------------------------------------