はじめに
- 機械学習用の画像を収集し、目視で仕分けする作業が発生する場合があります。
- Mac では、Finder で画像を見ながら削除を繰り返したりしますね。これは、かなり骨の折れる作業です。
- 今回は、Webブラウザベースの簡易管理アプリを作成しました。
- ソース一式は ここ です。
概要
-
トップページ
では、config.py
のCLASSES
を元に各メニューを表示しています。 -
ダウンロード画像、顔画像
では、両者を比較することができます。 - また、顔画像をクリックし、まとめて削除を実施できます。
-
学習画像の予測結果
とテスト画像の予測結果
は、別途提供する予定です。
トップページ
ダウンロード画像と顔画像
ライブラリ
-
Flask
を用いています。 -
Pillow
は、画像の拡大縮小に用いています。 -
Bootstrap
を用いています。 -
Font Awesome
で特殊なアイコンを表示しています。今回は、顔画像の右上に表示するゴミ箱のアイコンで利用しています。
トップページ
- トップページは、
index.html
の内容をほぼそのまま表示しています。 -
config.py
のCLASSES
は、index.html
のitems
として渡しています。 - 各メニューの識別のために、
Bootstrap
で色を変えています。
image_viewer.py
@app.route('/')
def index():
"""Top Page."""
return render_template('index.html', items=CLASSES)
templates/index.html
<div class="container-fluid">
{% for item in items %}
<div class="row">
<div class="col">
{{ loop.index }} {{ item }}
</div>
<div class="col-11">
<a class="btn btn-primary" href="/download_and_face/{{ item }}" role="button">ダウンロード画像、顔画像</a>
<a class="btn btn-secondary" href="/predict/train/{{ item }}" role="button">学習画像の予測結果</a>
<a class="btn btn-success" href="/predict/test/{{ item }}" role="button">テスト画像の予測結果</a>
</div>
</div>
<br />
{% endfor %}
</div>
ダウンロード画像と顔画像
-
DOWNLOAD_PATH
配下には、Google カスタム検索等でダウンロードした画像が保存されています。 - 例: data/download/安倍乙/0001.jpeg
-
FACE_PATH
配下には、OpenCV
のHaar Cascade
を用いて顔認識した画像が保存されています。 - また、ダウンロード画像のファイル名を元にファイル名を生成しています。
- 例: data/face/安倍乙/0001-0001.jpeg
GET の処理
-
安倍乙
などをitem
で受け取ります。 -
DOWNLOAD_PATH
item
*.jpeg
をキーとして、ダウンロード画像の一覧を作成します。 -
FACE_PATH
item
*.jpeg
をキーとして、顔画像の一覧を作成します。
image_viewer.py
@app.route('/download_and_face/<item>', methods=['GET', 'POST'])
def download_and_face(item):
"""ダウンロード画像、顔画像."""
download_list = glob.glob(os.path.join(DOWNLOAD_PATH, item, '*.jpeg'))
download_list = sorted([os.path.basename(filename) for filename in download_list])
face_list = glob.glob(os.path.join(FACE_PATH, item, '*.jpeg'))
face_list = sorted([os.path.basename(filename) for filename in face_list])
- ダウンロード画像から顔画像の検索キーを作成し、配列
row
を作成しています。 - 各ダウンロード画像と顔画像の組み合わせは、
rows
に再格納されます。 -
item
とrows
をテンプレートエンジンへ引き渡します。
image_viewer.py
rows = []
for download in download_list:
row = [download]
key = download.split('.')[0] + '-'
for face in face_list:
if face.startswith(key):
row.append(face)
rows.append(row)
return render_template('download_and_face.html', item=item, rows=rows)
- テンプレートでは、ダウンロード画像と顔画像の組みを 1行 で表示します。
- ダウンロード画像と顔画像のリンクを作成します。
-
size=200
で画像のサイズを指定しています。このサイズは、画像の縦のサイズとなります。 - サイズを揃える事で、ダウンロード画像と顔画像の比較がしやすくなります。
- 顔画像では、クリックで指定しやすい様に
CSS
とJS
を利用しています。 - こちらは、下記を参考にしました。
templates/download_and_face.html
<tbody>
{% for row in rows %}
<tr>
<td>
{{ loop.index }}
</td>
<td>
<figure class="figure">
<img src="/data/download/{{ item }}/{{ row[0] }}?size=200" />
<figcaption class="figure-caption">{{ row[0] }}</figcaption>
</figure>
</td>
<td>
{% for filename in row[1:] %}
<figure class="figure">
<label class="image-checkbox">
<img src="/data/face/{{ item }}/{{ filename }}?size=200" />
<input type="checkbox" name="filename" value="{{ filename }}" />
<i class="fa fa-trash-o d-none"></i>
</label>
<figcaption class="figure-caption">{{ filename }}</figcaption>
</figure>
{% endfor %}
</td>
</tr>
{% endfor %}
</tbody>
- 顔画像は、チェックボックスを指定した場合、指定しない場合の表示を調整しています。
static/download_and_face.css
.image-checkbox {
cursor: pointer;
border: 2px solid transparent;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
position: relative;
}
.image-checkbox input[type="checkbox"] {
display: none;
}
.image-checkbox-checked {
border-color: #d9534f;
}
.image-checkbox .fa {
color: #ffffff;
background-color: #d9534f;
font-size: 20px;
padding: 4px;
position: absolute;
right: 0;
top: 0;
}
.image-checkbox-checked .fa {
display: block !important;
}
- 初回表示の時、チェックボックスの状態をクラスに設定しています。
- また、クリック時にクラスを変更しています。
static/download_and_face.js
// image gallery
// init the state from the input
$(".image-checkbox").each(function () {
if ($(this).find('input[type="checkbox"]').first().attr("checked")) {
$(this).addClass('image-checkbox-checked');
}
else {
$(this).removeClass('image-checkbox-checked');
}
});
// sync the state to the input
$(".image-checkbox").on("click", function (e) {
$(this).toggleClass('image-checkbox-checked');
var $checkbox = $(this).find('input[type="checkbox"]');
$checkbox.prop("checked",!$checkbox.prop("checked"))
e.preventDefault();
});
画像のサイズの変更
- ダウンロード画像、顔画像のパスを生成します。
image_viewer.py
@app.route('/data/<folder>/<item>/<filename>')
def get_image(folder, item, filename):
"""画像のレスポンス size で拡大縮小."""
if folder not in ['download', 'face']:
abort(404)
filename = os.path.join(DATA_PATH, folder, item, filename)
-
Pillow
を利用して画像を読み込みます。
image_viewer.py
try:
image = Image.open(filename)
except Exception as err:
pprint.pprint(err)
abort(404)
- URL のオプションで
size
がある場合は、画像のサイズを修正します。 - 今回の場合は、縦のサイズを元に画像が拡大縮小します。
- 縦のサイズを、ダウンロード画像と顔画像で揃えると、目視で比較しやすくなりました。
- また、
Pillow
には、thumbnail
でサイズを変更することもできます。 - ただ、こちらは縦横の比率が変わってしまいます。
image_viewer.py
if 'size' in request.args:
height = int(request.args.get('size'))
width = int(image.size[0] * height / image.size[1])
image = image.resize((width, height), Image.LANCZOS)
- 最後に、
Pillow
のデータをバイトデータに変換し、image/jpeg
でレスポンスを作成します。
image_viewer.py
data = io.BytesIO()
image.save(data, 'jpeg', optimize=True, quality=95)
response = make_response()
response.data = data.getvalue()
response.mimetype = 'image/jpeg'
return response
POST の処理
- フォームで削除する顔画像のファイル名が POST されます。
- 対象の顔画像を確認し、
os.remove
で削除しています。 - ここは、もう少し慎重な仕組みが必要だと思いますが、個人で利用する事を前提に、簡易な形にしています。
image_viewer.py
@app.route('/download_and_face/<item>', methods=['GET', 'POST'])
def download_and_face(item):
"""ダウンロード画像、顔画像."""
if request.method == 'POST' and request.form.get('action') == 'delete':
for filename in request.form.getlist('filename'):
filename = os.path.join(FACE_PATH, item, filename)
if os.path.isfile(filename):
os.remove(filename)
print('delete face image: {}'.format(filename))
おわりに
- ダウンロード画像と顔画像を比較しながら、不要な顔画像を削除するWebアプリケーションを作成しました。
- Mac の Finder を用いて顔画像を削除するよりは、格段に便利になったと思います。
- Webアプリケーションの作成には、それなりに時間や慣れが必要ですね。
- 次回は、顔画像を水増しを実施する予定です。