前回は、Webに表示されたカメラ撮影画像を人間が分類して当該Dirに格納するアプリでした。
今回は、分類をVGG16という物体認識のCNN。。(ちなみにいろいろなPretrainedな物体認識CNNが使えます)に任せて、人間は撮影のボタンを押すだけで分類ができるという優れものです。
※Dirが無い場合は、ファイル名がその認識名+確率(%)としました
コードは以下に置きました
Video-Stream / categorize_sample.py
Video-Stream / templates / index4.html
Dir構成
flask_PJ
├── categorize_sample.py
|── templates
| └── index4.html
└── static
└── sample
| └── gazo....jpg #撮影画像格納
└── labeled #分類用Dir〇✖などを配下に作る
| └── gazo....jpg #Dirが無いものはここに配置される
| └── ....jpg
└── 〇
| └── gazo....jpg
| ...
| └── ....jpg
└── ✖
└── gazo....jpg
...
└── ....jpg
コード詳細
※煩雑になるので、前回からの差分中心に解説します
#importはkerasのネットワーク部分やVGG16を追加
@app.route("/")
def index():
img_path, category_path=yomikomi() #yomikomi関数に任せる
return render_template('index4.html', img=img_path[len(img_path)-1], category=category_path)
def yomikomi(): #VGG16利用のAutoLabelling関数
batch_size = 2
num_classes = 1000
img_rows, img_cols=224,224 #分析画像サイズ
input_tensor = Input((img_rows, img_cols, 3))
# 画像ファイルパスを取得
img_path = glob.glob("static/sample/*.jpg")
# 学習済みのVGG16をロード
# 構造とともに学習済みの重みも読み込まれる
model = VGG16(weights='imagenet', include_top=True, input_tensor=input_tensor)
# 煩雑なのでFineTuning版削除
model.summary()
# 引数で指定した画像ファイルを読み込む
# サイズはVGG16のデフォルトである224x224にリサイズされる
x = image.load_img(img_path[len(img_path)-1], target_size=(224, 224))
# 読み込んだPIL形式の画像をarrayに変換
x = image.img_to_array(x)
x = np.expand_dims(x, axis=0)
preds = model.predict(preprocess_input(x))
results = decode_predictions(preds, top=5)[0]
for result in results:
print(result)
#preds =str(results[0][1]) #Dirが適切に用意されている場合はこれで比較
if preds =="x":
preds= "./x"
elif preds =="〇":
preds="./〇"
elif preds =="△":
preds="./△"
else:
preds=str(results[0][1])+str(int(100*results[0][2]))+".jpg"
# 分類するフォルダ名・ファイル名を取得
category_path = preds
return img_path, category_path
@app.route("/sampling", methods=['POST'])
def sampling():
# 分類するフォルダパスを取得
category_path = os.path.join("static", os.path.join("labeled", request.form['category']))
# img_pathのファイルをcategory_pathに移動
shutil.move(request.form['img_path'], category_path)
...Camera撮影部分は前回と同じ
return redirect("/")
if __name__ == "__main__":
app.debug = True
app.run()
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="UTF-8">
<title>画像ラベリングサイト</title>
</head>
<body>
<h1>画像を分類してください</h1>
<img src="{{ img }}" style="width: 300px;">
<form action="{{ url_for('sampling') }}" method="POST">
<input type="hidden" name="img_path" value="{{ img }}">
<input type="submit" name="category" value="{{ category }}" >
#ラジオボタンが必要なくなった
</form>
</body>
</html>
コード解説
大きいところだけ説明します。
img_path, category_path=yomikomi() #yomikomi関数に任せる
これが一番大きな変更です。
今までのようにすべてをdef index()に書くのは無理があるので、人間がやっていたimg_pathとcategory_pathの読み込みを関数で切り出しました。
def yomikomi(): #VGG16利用のAutoLabelling関数
...
return img_path, category_path
VGG16利用のAutoLabelling関数です。
結果として、最後にimg_pathとcategory_pathを返します。
batch_size = 2
num_classes = 1000
img_rows, img_cols=224,224 #分析画像サイズ
input_tensor = Input((img_rows, img_cols, 3))
# 画像ファイルパスを取得
img_path = glob.glob("static/sample/*.jpg")
# 学習済みのVGG16をロード
# 構造とともに学習済みの重みも読み込まれる
model = VGG16(weights='imagenet', include_top=True, input_tensor=input_tensor)
いろいろ設定しています。以前のFineTuningの記事を参考にしてください。
batch_size: FineTuningで必要ですがPreTrainなモデルを使う場合いりません。サイズはメモリの都合で小さくしています。
num_class
: カテゴリー数です。VGG16などimagenet関係は1000クラス分類で学習されたPretrainedなモデルです。なお、FineTuning版では必要なクラス数としてください。
img_rows, img_cols=224,224
: 分析画像サイズです。VGG16は(224,224)で学習しています。これもFineTuningでは適当なサイズとできます。
img_path = glob.glob("static/sample/*.jpg")
:このコードは前回のコードと同じです。static/sample/配下のjpgファイルを読み込んでいます。
model = VGG16(weights='imagenet', include_top=True, input_tensor=input_tensor)
: 学習済みのVGG16をロード、構造とともに学習済みの重みも読み込まれる
model.summary()
:モデルの要約を出力しています。実行時は必要ありません。
おまけに掲出した出力が得られます。
# 引数で指定した画像ファイルを読み込む
# サイズはVGG16のデフォルトである224x224にリサイズされる
x = image.load_img(img_path[len(img_path)-1], target_size=(224, 224))
# 読み込んだPIL形式の画像をarrayに変換
x = image.img_to_array(x)
x = np.expand_dims(x, axis=0)
: 入力画像ファイルの前処理部分です。
サイズ(224,224)で読み込んで配列に変換後、numpy.expand_dimsのような変換をしています。
preds = model.predict(preprocess_input(x))
results = decode_predictions(preds, top=5)[0]
for result in results:
print(result)
#preds =str(results[0][1]) #Dirが適切に用意されている場合はこれで比較
preds = model.predict(preprocess_input(x))
:ここでmodelを使って予測しています。preprocess_input(x)でデータ成型をしています。なくても認識します、そして無い方がいい場合もあります。その場合単にxを代入する。
results = decode_predictions(preds, top=5)[0]
:predsで得られた結果を名称に変換して以下のフォーマットで出力します。
('n03793489', 'mouse', 0.5281165)
('n04442312', 'toaster', 0.09753004)
('n03777754', 'modem', 0.037513856)
('n04074963', 'remote_control', 0.037350822)
('n04131690', 'saltshaker', 0.027572514)
preds =str(results[0][1])
: Dirが適切に用意されている場合はこの変換をして比較します。変換をしていない場合は、以下の最後の行が実行されます。
if preds =="x":
preds= "./x"
elif preds =="〇":
preds="./〇"
elif preds =="△":
preds="./△"
else:
preds=str(results[0][1])+str(int(100*results[0][2]))+".jpg"
preds=str(results[0][1])+str(int(100*results[0][2]))+".jpg"
: 上記resultsの一番確率の高いものの第二第三要素をつなげてファイル名としています。
※【蛇足】元々Dir作成しておく方が効率的な場合もありますが、あとで適当なDir作成して移動できます。
def sampling():
は前回と同じです。img_pathとcategory_pathが前回と内容が少し異なりますが、これで無事に移動してくれます。なお、他のDirに配置したいなどcategory_pathを変更したい場合はここで追加処理するのがよいと思います。
index.html
: ラジオボタンが必要なくなったので対応部分をごそっと削除しました。
まとめ
・Pretrainedな物体認識モデルVGG16で自動でカメラ撮影画像ラベリングを実施してファイル名としてWeb表示・保管するWebアプリを作成した
・1060 3GBだとメモリ枯渇が発生して3回位で再起動が必要になるので、RasPiで使うにはもう一工夫必要である。
おまけ
VGG16モデル構造
____________________________________________________________
Layer (type) Output Shape Param #
============================================================
input_1 (InputLayer) (None, 224, 224, 3) 0
____________________________________________________________
block1_conv1 (Conv2D) (None, 224, 224, 64) 1792
____________________________________________________________
block1_conv2 (Conv2D) (None, 224, 224, 64) 36928
____________________________________________________________
block1_pool (MaxPooling2D) (None, 112, 112, 64) 0
____________________________________________________________
block2_conv1 (Conv2D) (None, 112, 112, 128) 73856
____________________________________________________________
block2_conv2 (Conv2D) (None, 112, 112, 128) 147584
____________________________________________________________
block2_pool (MaxPooling2D) (None, 56, 56, 128) 0
____________________________________________________________
block3_conv1 (Conv2D) (None, 56, 56, 256) 295168
____________________________________________________________
block3_conv2 (Conv2D) (None, 56, 56, 256) 590080
____________________________________________________________
block3_conv3 (Conv2D) (None, 56, 56, 256) 590080
____________________________________________________________
block3_pool (MaxPooling2D) (None, 28, 28, 256) 0
____________________________________________________________
block4_conv1 (Conv2D) (None, 28, 28, 512) 1180160
____________________________________________________________
block4_conv2 (Conv2D) (None, 28, 28, 512) 2359808
____________________________________________________________
block4_conv3 (Conv2D) (None, 28, 28, 512) 2359808
____________________________________________________________
block4_pool (MaxPooling2D) (None, 14, 14, 512) 0
____________________________________________________________
block5_conv1 (Conv2D) (None, 14, 14, 512) 2359808
____________________________________________________________
block5_conv2 (Conv2D) (None, 14, 14, 512) 2359808
____________________________________________________________
block5_conv3 (Conv2D) (None, 14, 14, 512) 2359808
____________________________________________________________
block5_pool (MaxPooling2D) (None, 7, 7, 512) 0
____________________________________________________________
flatten (Flatten) (None, 25088) 0
____________________________________________________________
fc1 (Dense) (None, 4096) 102764544
____________________________________________________________
fc2 (Dense) (None, 4096) 16781312
____________________________________________________________
predictions (Dense) (None, 1000) 4097000
============================================================
Total params: 138,357,544
Trainable params: 138,357,544
Non-trainable params: 0
____________________________________________________________