FC2ブログ

頭と尻尾はくれてやる!

パソコンおやじのiPhoneアプリ・サイト作成・運営日記


KerasのImageDataGeneratorで画像データの水増し

3チャンネル画像だとKerasとMPSの計算が合わない
↑この続き。
Kerasで学習のやり直しをすることに。せっかくなので画像データの水増しを試してみた。
TensorFlowでも画像データを増やせる関数などがあったがKerasにもImageDataGeneratorというものがあるようで取り入れてみた。

画像の前処理 - Keras Documentation
↑公式はこちら。

修正部分はこんな感じ↓
from keras.preprocessing.image import ImageDataGenerator

datagen = ImageDataGenerator(
                             rotation_range = 3 , 
                             width_shift_range = 0.1,
                             height_shift_range = 0.1,
                             zoom_range = 0.02,
                             channel_shift_range = 10.0, 
                             )#—(1)

datagen.fit(x_train)#—(2)


#history = model.fit(x_train, y_train, batch_size=batch_size, … #—(3)
history = model.fit_generator(
                              datagen.flow(x_train, y_train, batch_size=batch_size),…#—(4)

↑(1)ImageDataGeneratorで目的に合致するもので使えそうなのを選んで設定。
どのように画像が変化するのかは参考ページなどに。
(2)は内容によっては不要かも。
ImageDataGeneratorを使う前は(3)のfit関数で訓練を実行していたのだけど、(4)の形式に変更。

調子に乗って盛り込みすぎると学習が進まなくなってしまったので、ほどほどがよさそう、、、ほどほどってなんやねん!?将来はそういうのもKerasが良きに計らってくれたらいいのにな。


参考ページ
Kerasによるデータ拡張 - 人工知能に関する断創録
Kerasでデータ拡張(Data Augmentation)後の画像を表示する - Qiita


3チャンネル画像だとKerasとMPSの計算が合わない

画像の判別がCreateMLでダメだったのでKerasでやってみた
↑この続き。
Kerasで画像分類をさせて、その学習結果(重み、バイアス)を出力。それをmacOSアプリのMetal Performance Shadersで同じ計算をさせる。

計算量削減のために画像のRGBの3チャンネル分のデータを使う。するとKerasとMPSで同じ画像を与えても出てくる結果が違ってしまう。

ちなみに4チャンネルの画像を使い、同じようにKerasで学習させて係数をmacOSで使いMPSで計算させると同じ値になる。
よって、学習結果の係数の受け渡し方に問題はないと考えている。

MPSで画像データを与える場合に
- (void)replaceRegion:(MTLRegion)region mipmapLevel:(NSUInteger)level withBytes:(const void *)pixelBytes bytesPerRow:(NSUInteger)bytesPerRow;
↑このメソッドを使うとする(※1)。
//(1)4チャンネルの場合

{
    MTLRegion region = MTLRegionMake2D(0, 0, Width, Height);

    [srcImage.texture replaceRegion:region 
        mipmapLevel:0 
        withBytes:inputs 
        bytesPerRow:sizeof(unsigned char)*Width*NofChannel];
}
↑MPSではこんな感じでデータ列(ここではunsigned char型)をMPSImageのsrcImageのテクスチャに書き込む。
その範囲をMTLRegionで指定したり、1行あたりのバイト数を指定したりする。
これは意図通りに動いた。

問題は次の3チャンネルの場合だ。
//(2)3チャンネルの場合
{
    MTLRegion region = MTLRegionMake2D(0, 0, Width*3/4, Height);

    [srcImage.texture replaceRegion:region 
        mipmapLevel:0 
        withBytes:inputs 
        bytesPerRow:sizeof(unsigned char)*Width*NofChannel];
}
↑今回のケースではMPSImageは4チャンネルが基本(※2)なのだが、データは3チャンネルなのでbytesPerRowは1バイトx3チャンネルx横幅。
これに合わせてコピーする範囲の横幅をピクセル数の3/4倍としている(画像サイズは96x96なので横幅は4の倍数)。

これが、なぜかKerasと計算結果が全然合わない、、、😥

regionの設定が間違っているのか?と思いsrcImage.textureから再度データを取り出して画像を作成して元画像になってるのを確認したり、RGBが入れ替わっているのか?と思いチェックするなど、まあいろいろと調べたのだが、、、降参!

回避策は4チャンネルで学習、予測を行う、、、だ、ださいがまあ動くからいいか、、、なにこの敗北感。
何かあればお教えください。


※1
- (void)replaceRegion:(MTLRegion)region mipmapLevel:(NSUInteger)level slice:(NSUInteger)slice withBytes:(const void *)pixelBytes bytesPerRow:(NSUInteger)bytesPerRow bytesPerImage:(NSUInteger)bytesPerImage;
↑こちらのメソッドもあるが、今回は3チャンネルか4チャンネルなのでスライスは不要。

※2 これはMPSImageオブジェクトのpixel format次第。これはread only。MPSImageオブジェクトを作る時に使うMPSImageDescriptorオブジェクトの設定により自動的に決まるのだと思う。
MPSImageDescriptor *sourceImageDiscriptor = [MPSImageDescriptor imageDescriptorWithChannelFormat:MPSImageFeatureChannelFormatUnorm8 
        width:Width 
        height:Height 
        featureChannels:NofChannel];
↑このようにして作成するとsourceImageDiscriptor.pixelFormatは
MTLPixelFormatRGBA8Unorm
となり、
Ordinary format with four 8-bit normalized unsigned integer components in RGBA order
↑とリファレンスにあるように8ビット符号なし整数のRGBAの値を持つ(4バイト)。

ちなみにMNISTでは白黒画像を使っていたが
MPSImageDescriptor *sourceImageDiscriptor = [MPSImageDescriptor imageDescriptorWithChannelFormat:MPSImageFeatureChannelFormatUnorm8 
width:28 
height:28 
featureChannels:1];
↑このように画像を設定した場合だとpixel formatは
MTLPixelFormatR8Unorm
となり1ピクセルに1バイトの値しか持たない。





NSImage画像をRGBAのデータに展開する

カラー(※1)のNSImage画像(RGBAの4チャンネルのデータを持つ)をMetal Performance Shadersで使うためにデータ化する、というのをしたことがなかったのでデータ化するクラスメソッドを作った。

この手のメソッドは数ヶ月後か数年後かに参考にしたり使い回ししたくなるものなので可能ならクラスメソッドにして自分がすぐにわかるような置き場に置いておくようにしたい(自分への戒め)。

あらかじめその分のメモリは確保しておく必要がある。
// 呼び出し元
{
    unsigned char *toData = calloc(width*height*4, sizeof(unsigned char));
    [MyUtility storeColorImage:image to:toData];

    // (toDataを使う処理)

    free(toData);//—(1)
}

// MyUtilityクラスの実装ファイル
+(void)storeColorImage:(NSImage*)image to:(unsigned char *)toData
{
    size_t width = image.size.width;
    size_t height = image.size.height;
    size_t bitsPerComponent = 8;
    size_t bytesPerRow = width*4;
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGImageAlphaInfo bitmapInfo = kCGImageAlphaNoneSkipLast;
    
    CGContextRef context = CGBitmapContextCreate(nil,
                                                 width,
                                                 height,
                                                 bitsPerComponent,
                                                 bytesPerRow,
                                                 colorSpace,
                                                 bitmapInfo);
    
    NSRect imageRect = NSMakeRect(0, 0, width,height);
    NSGraphicsContext *gctx = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
    [NSGraphicsContext setCurrentContext:gctx];
    [image drawInRect:imageRect];
    
    void *data = CGBitmapContextGetData (context);
    
    memcpy(toData, data, width*height*4);
    
    CGColorSpaceRelease(colorSpace);
    CGContextRelease(context);
}
(1)使用後はメモリの解放が必要。そういう事情もあるので
+(unsigned char *)getDataForImage:(NSImage *)image;
という形式にできない(※2)のが少し残念、というかコードの可読性が低下するが仕方ない。


※1 わざわざ”カラー”としているのは、白黒も扱うことがあるのであえてメソッド名などにColorと入れてるだけ
※2 できなくもないけどメモリ解放を忘れてしまう可能性大なので避けてる


画像の判別がCreateMLでダメだったのでKerasでやってみた

CreateMLで2種類の画像を見分けられなかった話
↑ここでCreateMLだと2種類の画像の分類がうまくいかなかったことを書いた。
転移学習を利用しているCreateMLだと動物の分類などでは枚数がすくなくてもうまくいくが、今回のような課題には向かないのだろう、と思いCreateMLでやるのはあきらめた。

ただ、機械学習で見分けられない課題ではないだろう?と思ったのでKeras(TensorFlowバックエンド)で分類できるか学習させてみた。

NNの構成はCNNを使うMNISTと同じで、二層の畳み込み、ドロップアウトもありのまあサンプルコード通りのものだ。
実際に動かしてみたら(私のMacには)300x300の画像は大きすぎたようで、あらかじめ全ての画像を96x96にリサイズしておいた。

判定対象画像1

判定対象画像2

↑このサイズだとこのような感じ。Excellent!の文字の有無を判定する。

その結果のログがこちら↓

Kerasでの学習結果

Train 99.23%
Test 100.0%

いけるやんかー!!!そうだよな、CNN、お前はできる子や!
5epochだけやったようなログになっているが、この前にもやっているので実際には合計で30epoch弱程度やっている。
次はこの学習結果をmacOSアプリで利用できるようにせねば。






CreateMLで2種類の画像を見分けられなかった話

ポケモンGOでエクセレントスローの瞬間って気持ちいいよね?



↑こういうの。カーブスローでエクセレントって気持ちいい!
なので、その部分ばかりを集めた動画を作成しようと数ヶ月かけてたくさんの動画を溜めてる。
そろそろ編集しようと思ったんだけど、結構大量にあるので大変。

そこで、こういう動画の中でExcellent!と表示された瞬間を含む数秒を探し切り取る、というのを自動化しようと思った。

CreateMLを使い、動画内の画像にExcellent!と表示されてるか否かを判断するするよう学習させたい。

そのためにはまず学習用の画像が必要、ということでサイズが300x300の画像をそれぞれ651枚ずつの計1,302枚を用意した(※1)。

Excellentがない場合のサンプル画像

↑一つはExcellent!という文字がない画像。

Excellentがない場合のサンプル画像概要
↑動画内にはいろんな場面が含まれる。

Excellentがある場合のサンプル画像

↑もう一つはExcellent!という文字を明らかに(人間の目で見て)含む画像。

Excellentがある場合のサンプル画像概要
↑先ほどのほどいろいろあるわけではないが、時間帯やタイミングなどでいろんな画像になる。

この二つを判別させたいわけだ。


Create MLで学習させmlmodelファイルを得る
↑これと同様にして学習させる。今回の場合はCrop, Expose, Noiseにチェックを入れた↓

CreateMLでの初期設定スクショ

学習開始から2時間28分で学習完了。

CreateMLでの学習結果

↑その結果がこれ。

Training 72%
Validation 68%
別途用意したテストデータ(40枚)に対して55%て、、、
全然ダメじゃないか、、、😩


データ数不足?いや、動物を見分けるのにもっと少ないサンプルでできてる例(※2)がある。
CropやExposeを有効にしてるからもっと学習したらよくなるんじゃないの?などと考えたりしていたのだが、、、

Create MLでMNISTの分類器を作ってみた - Galapagos Blog
↑こちらを読んでると、今回の課題自体がCreateMLに向いていないのか?という気がする。CreateMLは転移学習らしいので、形状を見分けるような課題に対してはいい結果がでるが、今回のような文字の有無を見分けるという課題にはそぐわないのかもしれない。



※1 機械学習用の画像作成のために、動画のある場所からある場所までで画像を作成するmacOSアプリを自作した。

機械学習用画像の作成ツールスクショ

↑こんな感じで1がExcellent!が出ているところで、0は出ていないところ(画像は撮影用なのでずれてるけど)。動画の全体の中でExcellent!が表示されている時間は短いので、0に相当する部分は間引いて両方の枚数が同じになるようにしている。


※2 参考サイト
Create MLで簡単に機械学習を体験する - Qiita
CreateMLを使ってさらっと画像認識させてみた - Qiita









Copyright ©頭と尻尾はくれてやる!. Powered by FC2 Blog. Template by eriraha.

FC2Ad