scale random cropをKerasのImageDataGeneratorを継承して実装 - 4月 02, 2019 こんにちは、ぐぐりら(@guglilac)です。 Kerasの ImageDataGenratorには色々画像に関連する前処理をやってくれる機能が入っていて便利なのですが, random cropがなくて困ったので自作しました。 と言ってもrandom crop自体は僕が一から書いたわけではなく、[KerasのImageDataGeneratorを継承してMix-upやRandom Croppingのできる独自のジェネレーターを作る - Qiita](https://qiita.com/koshian2/items/909360f50e3dd5922f32)を参考にして書きました。 ただ、この実装だとrandom cropするだけで、画像の拡大とかは外側でやらなくてはいけないのであまり綺麗じゃないな、と思ったので改良しました。 受け取ったnumpy配列の画像(画素値は0-255,RGB)をexpand_rateに従って拡大し、元の画像のサイズになるようにrandom cropする、という内容になります。 仕組みとしてはこんな感じ。 1. 元クラスのimage data generatorがまず変換を行う 1. 次に、自作したscale random cropを使うようにする。 画像の拡大をnumpyのままやるのは難しそうだから、他の画像処理系ライブラリを使って拡大してからそれをnumpyに戻して、random cropすればいいと思ってやってみた 最初はこんな感じで書いた ```python dy, dx = original_img.shape[0:2] expanded_img = np.asarray(Image.fromarray(np.uint8(original_img)).resize((int(dy * self.expand_rate), int(dx * self.expand_rate)))) ``` こんな感じでやってた。 元のimage data generatorが先に処理をするので、rescaleが先に入ってしまい、original_imgがすでに0-1になっていて、np.uint8を使うと0 or 1にバイナリになってしまう。結構ひどい感じになる。 np.uint8がいけないわけだけれど、これを使わないとnumpy配列をPIL画像に戻せなくて困ってしまった。 ## 解決策1 random cropする場合はrescaleを元のimage data generatorでは行わず、random cropしたあとにrescaleするようにするというダサいやり方 ```python class MyImageDataGenerator(ImageDataGenerator): """画像の前処理を行うImageDataGeneratorにrandom cropを実装したクラス.""" def __init__(self, featurewise_center=False, samplewise_center=False, featurewise_std_normalization=False, samplewise_std_normalization=False, zca_whitening=False, zca_epsilon=1e-06, rotation_range=0.0, width_shift_range=0.0, height_shift_range=0.0, brightness_range=None, shear_range=0.0, zoom_range=0.0, channel_shift_range=0.0, fill_mode='nearest', cval=0.0, horizontal_flip=False, vertical_flip=False, rescale=None, preprocessing_function=None, data_format=None, validation_split=0.0, random_crop=True, expand_rate=1.2): self.my_rescale = rescale if random_crop: rescale = None # random_cropをするときに先にrescaleしているとnp.uint8にcastした時バイナリになってしまうため、あとでrescaleする # 親クラスのコンストラクタ super().__init__(featurewise_center, samplewise_center, featurewise_std_normalization, samplewise_std_normalization, zca_whitening, zca_epsilon, rotation_range, width_shift_range, height_shift_range, brightness_range, shear_range, zoom_range, channel_shift_range, fill_mode, cval, horizontal_flip, vertical_flip, rescale, preprocessing_function, data_format, validation_split) self.random_crop = random_crop self.expand_rate = expand_rate def scale_random_crop(self, original_img, seed): """一枚の画像を拡大してrandom cropする. :params original_img: (height,width,channels)をshapeにもつnumpy array :params seed: random cropする際の場所に関するrandom seed :return cropped image: sizeはoriginal_imgと同じになるようにcropする. """ np.random.seed(seed) assert original_img.shape[2] == 3 # the number of channels dy, dx = original_img.shape[0:2] expanded_img = np.asarray(Image.fromarray(np.uint8(original_img)).resize((int(dy * self.expand_rate), int(dx * self.expand_rate)))) height, width = expanded_img.shape[0:2] x = np.random.randint(0, width - dx + 1) y = np.random.randint(0, height - dy + 1) return expanded_img[y:(y + dy), x:(x + dx), :] def flow(self, x, y=None, batch_size=32, shuffle=True, sample_weight=None, seed=None, save_to_dir=None, save_prefix='', save_format='png', subset=None): batches = super().flow(x=x, y=y, batch_size=batch_size, shuffle=shuffle, sample_weight=sample_weight, seed=seed, save_to_dir=save_to_dir, save_prefix=save_prefix, save_format=save_format, subset=subset) # 拡張処理 while True: batch_x = next(batches) # Scale Random crop if self.random_crop: x = np.zeros(batch_x.shape) for i in range(batch_x.shape[0]): x[i] = self.scale_random_crop(batch_x[i], seed) batch_x = x * self.my_rescale # random cropする場合はcropしたあとにrescaleする yield batch_x ``` ## 解決策2 Kerasのarray_to_img,img_to_arrayを使ってみると、少しだけましになる。 ```python expanded_img = img_to_array(array_to_img(original_img).resize((int(dy * self.expand_rate), int(dx * self.expand_rate)))) ``` `array_to_img`はnumpy配列以外に、scaleという引数を受け取ることができます。 scaleがtrueだと、0-1に正規化されたnumpy配列をもらった時に自動で0-255に戻してPIL画像に変換してくれます。 今回の例でいうと、rescaleを先に走らせて0-1に正規化してしまっていても、array_to_imgを使えば問題なくPIL画像に戻せます。 なので、解決策1でinitの中で苦し紛れにrescaleを退避させていた部分を書かなくて済むようになりました! ただ、img_to_arrayでnumpy配列に戻した時は0-255になってしまっているので再度0-1にrescaleする必要があるのが難点。。。 ```python batch_x = x * self.rescale if self.rescale is not None else x # random cropするときに0-255に戻った分を再度rescale ``` 継承元のImageDataGeneratorに`self.rescale`として保持されているみたいなのでこれで再度rescaleします。 ## まとめ * [KerasのImageDataGeneratorを継承してMix-upやRandom Croppingのできる独自のジェネレーターを作る - Qiita](https://qiita.com/koshian2/items/909360f50e3dd5922f32)を、画像を拡大してrandom cropするように修正 * 画像の拡大をPIL画像でやろうとする段階で少しイケてない書き方になった rescaleが先に走ってしまうので、random cropしたあとにもう一度rescaleをするという書き方になってしまいました。うーん。 もう少しいい書き方があれば教えていただきたいです。 (というかKerasのImageDataGeneratorがrandom cropをしてくれないのがなあ) この記事をシェアする Twitter Facebook Google+ B!はてブ Pocket Feedly コメント
コメント
コメントを投稿