今回は以下のコンペで画像認識の勉強をしてみました。
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])]) #---------------------------------------------------------------------------------------------------------------