LoginSignup
0
0

More than 3 years have passed since last update.

ダウンロード画像と顔画像の簡易管理アプリ

Last updated at Posted at 2019-12-13

はじめに

  • 機械学習用の画像を収集し、目視で仕分けする作業が発生する場合があります。
  • Mac では、Finder で画像を見ながら削除を繰り返したりしますね。これは、かなり骨の折れる作業です。
  • 今回は、Webブラウザベースの簡易管理アプリを作成しました。
  • ソース一式は ここ です。

概要

  • トップページでは、config.pyCLASSES を元に各メニューを表示しています。
  • ダウンロード画像、顔画像では、両者を比較することができます。
  • また、顔画像をクリックし、まとめて削除を実施できます。
  • 学習画像の予測結果テスト画像の予測結果は、別途提供する予定です。

トップページ

image.png

ダウンロード画像と顔画像

image.png

ライブラリ

  • Flask を用いています。
  • Pillow は、画像の拡大縮小に用いています。
  • Bootstrap を用いています。
  • Font Awesome で特殊なアイコンを表示しています。今回は、顔画像の右上に表示するゴミ箱のアイコンで利用しています。

トップページ

  • トップページは、index.html の内容をほぼそのまま表示しています。
  • config.pyCLASSES は、index.htmlitems として渡しています。
  • 各メニューの識別のために、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 配下には、OpenCVHaar 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 に再格納されます。
  • itemrows をテンプレートエンジンへ引き渡します。
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 で画像のサイズを指定しています。このサイズは、画像の縦のサイズとなります。
  • サイズを揃える事で、ダウンロード画像と顔画像の比較がしやすくなります。
  • 顔画像では、クリックで指定しやすい様に CSSJS を利用しています。
  • こちらは、下記を参考にしました。
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アプリケーションの作成には、それなりに時間や慣れが必要ですね。
  • 次回は、顔画像を水増しを実施する予定です。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0