Explore and run machine learning code with Kaggle Notebooks | Using data from Bengali.AI Handwritten Grapheme Classification
Image preprocessing (128x128)
Kaggleのベンガル語のコンペをやっていますが,機械学習始めたてで画像処理に対してどのようなPreProcessingをやっているか勉強も兼ねて調査しています.
今回は「Image preprocessing (128x128)」を模倣してみました.
Description
文字のアスペクト比を維持しながら128x128のサイズにクロップしています.シンプルなコードでクロップが出来ることを知りました.
Code
必要なパッケージを読み込みます
from tqdm import tqdm import zipfile import cv2 import matplotlib.pyplot as plt %matplotlib inline
次にデータを読み込みます.ここではオリジナルの画像サイズとリサイズ後のサイズを指定しています.
HEIGHT = 137 WIDTH = 236 SIZE = 128 TRAIN = ['/kaggle/input/bengaliai-cv19/train_image_data_0.parquet', '/kaggle/input/bengaliai-cv19/train_image_data_1.parquet', '/kaggle/input/bengaliai-cv19/train_image_data_2.parquet', '/kaggle/input/bengaliai-cv19/train_image_data_3.parquet'] OUT_TRAIN = 'train.zip'
今回の肝のコードになります.入力画像の角(4箇所)を取得するbbox()メソッドと,実際に画像をクロップしてリサイズするcrop_resize()メソッドになります.
def bbox(img): # anyメソッドで行・列ごとに輝度がALL0かそれ以外かを計算, TrueかFalseで返ってくる rows = np.any(img, axis=1) cols = np.any(img, axis=0) rmin, rmax = np.where(rows)[0][[0, -1]] # rowsのTrueの最初と最後を取得 cmin, cmax = np.where(cols)[0][[0, -1]] # colsのTrueの最初と最後を取得 return rmin, rmax, cmin, cmax def crop_resize(img0, size=SIZE, pad=16): #crop a box around pixels large than the threshold #some images contain line at the sides # 最初から5pix後と最後から5pix前の範囲において,80以上の輝度をTrueに変換してbboxに渡す # 80以下はFalseに変換される ymin,ymax,xmin,xmax = bbox(img0[5:-5,5:-5] > 80) #cropping may cut too much, so we need to add it back # クロッピングがカットしすぎる(負の値になるorオリジナルより大きくなる)可能性もあるので,それを考慮した処理をする xmin = xmin - 10 if (xmin > 10) else 0 ymin = ymin - 10 if (ymin > 10) else 0 xmax = xmax + 10 if (xmax < WIDTH - 10) else WIDTH ymax = ymax + 10 if (ymax < HEIGHT - 10) else HEIGHT # オリジナルからクロップ出来た範囲を指定して新しいimgを作成する img = img0[ymin:ymax,xmin:xmax] #remove lo intensity pixels as noise # 28以下の輝度を消去する(img0から新しいimgを作っていることに注意) img[img < 28] = 0 # 横幅と縦幅を取得 lx, ly = xmax-xmin,ymax-ymin # 横幅または縦幅の大きい方にpaddingする値を足し算 l = max(lx,ly) + pad #make sure that the aspect ratio is kept in rescaling # rescaleするときはアスペクト比を維持しなければならない # 0埋めする長さを計算,横幅の計算は縦の長さを使う(逆も然り).そして縦横のimgのサイズを同じにする. # 上下で埋めるので2で割った余りを計算している w_pad = (l-ly)//2, h_pad = (l-lx)//2, img = np.pad(img, [w_pad, h_pad], mode='constant') return cv2.resize(img,(size,size))
実際にテストデータ0を読み込んで,動作を確認する
df = pd.read_parquet(TRAIN[0]) n_imgs = 8 fig, axs = plt.subplots(n_imgs, 2, figsize=(10, 5*n_imgs)) for idx in range(n_imgs): #somehow the original input is inverted # 背景が暗く,文字が白いので数値が逆転していたよう # 255で逆算する.(1~8行目のサンプルの画像に対して行っている) img0 = 255 - df.iloc[idx, 1:].values.reshape(HEIGHT, WIDTH).astype(np.uint8) #normalize each image by its max val # 画像内の最大値を255に揃えてる(例えば,画像内の最大値が230の場合は,全体を1.1倍くらいして230を255になるように揃える) img = (img0*(255.0/img0.max())).astype(np.uint8) img = crop_resize(img) axs[idx,0].imshow(img0) axs[idx,0].set_title('Original image') axs[idx,0].axis('off') axs[idx,1].imshow(img) axs[idx,1].set_title('Crop & resize') axs[idx,1].axis('off') plt.show()
たしかにクロップされ,アスペクト比を維持したままリサイズされていることがわかる
zipfileを使ってクロップ,リサイズした訓練データの結果をzip圧縮する
x_tot,x2_tot = [],[] with zipfile.ZipFile(OUT_TRAIN, 'w') as img_out: for fname in TRAIN: df = pd.read_parquet(fname) #the input is inverted data = 255 - df.iloc[:, 1:].values.reshape(-1, HEIGHT, WIDTH).astype(np.uint8) for idx in tqdm(range(len(df))): name = df.iloc[idx,0] #normalize each image by its max val img = (data[idx]*(255.0/data[idx].max())).astype(np.uint8) img = crop_resize(img) x_tot.append((img/255.0).mean()) x2_tot.append(((img/255.0)**2).mean()) img = cv2.imencode('.png',img)[1] img_out.writestr(name + '.png', img)
正規化するためにmeanとstdを計算
#image stats img_avr = np.array(x_tot).mean() img_std = np.sqrt(np.array(x2_tot).mean() - img_avr**2) print('mean:',img_avr, ', std:', img_std)
備考
Q. Great kernel! Quick question: how did you figure out the images are inverted and to normalize them? (最強のカーネルだ!ちょっといいかい.画像が反転してんのかってのとそれを正規化する方法をどうやってみつけたんだ?)
When I added 0 padding I realized that the background color corresponds to 255 rather than 0. Most likely it doesn't affect anything (just a little bit more convenient), but I prefer to have data similar to other handwriting recognition competitions I checked. (0埋めしたときに背景色が0じゃなくて255に対応してるってことに気づいちゃったのさ.まぁ特に問題はないんだけど,他の手書き文字コンペと同じようにしたいから反転しただけだよ)
Regarding normalizing, I saw several images with very low max value, like 116, while most images have high max value ~255. I think it corresponds to pressure on the pen during writing. So, normalizing the input by the maximum value of the pressure sounds reasonable in case if some people didn't press the pen hard enough. Also, in this case the thresholds used for cropping the images and noise elimination would not discourage such low intensity images. (ノーマライズするときにいくつかの訓練画像にマックスバリューが小さいものが混ざってるのに気づいたんだ,例えば116とか.逆にほとんどの画像はマックスバリューが255だったんだけどね.文字を書くときの圧の違いが関係してる気がするんだ.だから入力を最大値で正規化して誰かが強く書いた文字にも対応できるようにしたんだ....)








