[PYTHON] Simple management app for download images and face images

Introduction

――It may be necessary to collect images for machine learning and sort them visually. --On Mac, you repeatedly delete images while viewing them in the Finder. This is a daunting task. --This time, we created a web browser-based simple management application. --The complete source is here.

Overview

--On the top page, each menu is displayed based on CLASSES in config.py. ――In Download image, face image, you can compare both. ――You can also click the face image to delete all at once. --The prediction result of the training image and the prediction result of the test image will be provided separately.

top page

image.png

Download image and face image

image.png

Library

--I'm using Flask. --Pillow is used for scaling the image. --I'm using Bootstrap. --A special icon is displayed with Font Awesome. This time, I am using the trash can icon displayed in the upper right of the face image.

top page

--The top page displays the contents of ʻindex.html almost as it is. --CLASSES in config.py is passed as ʻitems in ʻindex.html. --The colors are changed with Bootstrap` to identify each menu.

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">Download image, face image</a>
          <a class="btn btn-secondary" href="/predict/train/{{ item }}" role="button">Prediction result of training image</a>
          <a class="btn btn-success" href="/predict/test/{{ item }}" role="button">Prediction result of test image</a>
        </div>
      </div>
      <br />
      {% endfor %}
    </div>

Download image and face image

--Under DOWNLOAD_PATH, images downloaded by Google Custom Search etc. are saved. --Example: data / download / Oto Abe /0001.jpeg --Under FACE_PATH, the image recognized by face using Haar Cascade of ʻOpenCV` is saved. --In addition, the file name is generated based on the file name of the downloaded image. --Example: data / face / Oto Abe /0001-0001.jpeg

GET processing

--You will receive Abe Oto etc. at ʻitem. --Create a list of downloaded images using DOWNLOAD_PATH ʻitem`` * .jpeg as a key. --Create a list of face images using FACE_PATH ʻitem`` * .jpeg` as a key.

image_viewer.py


@app.route('/download_and_face/<item>', methods=['GET', 'POST'])
def download_and_face(item):
    """Download image, face image."""

    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])

--The search key for the face image is created from the downloaded image, and the array row is created. --Each download image and face image combination is re-stored in rows. --Pass ʻitem and rows` to the template engine.

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)

--The template displays the combination of the downloaded image and the face image on one line. --Create a link between the downloaded image and the face image. --The size of the image is specified by size = 200. This size is the vertical size of the image. ――By matching the sizes, it will be easier to compare the downloaded image and the face image. --For face images, CSS and JS are used so that they can be easily specified by clicking. ――Here, I referred to the following. - Bootstrap image checkbox

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>

--For the face image, the display is adjusted when the check box is specified and when it is not specified.

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;
}

--At the time of the first display, the state of the check box is set to the class. --Also, the class is changed when clicked.

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();
});

Resize image

--Generate download image and face image paths.

image_viewer.py


@app.route('/data/<folder>/<item>/<filename>')
def get_image(folder, item, filename):
    """Scale with image response size."""

    if folder not in ['download', 'face']:
        abort(404)

    filename = os.path.join(DATA_PATH, folder, item, filename)

--Use Pillow to load the image.

image_viewer.py


    try:
        image = Image.open(filename)
    except Exception as err:
        pprint.pprint(err)
        abort(404)

--If there is a size in the URL option, modify the size of the image. --In this case, the image will be scaled based on the vertical size. ――By aligning the vertical size with the downloaded image and the face image, it is easier to compare visually. --You can also resize Pillow with thumbnail. ――However, the aspect ratio changes here.

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)

--Finally, convert the data of Pillow to byte data and create a response with ʻ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 processing

--The file name of the face image to be deleted in the form is POSTed. --Check the target face image and delete it with ʻos.remove`. ――I think that a more careful mechanism is required here, but it is a simple form on the assumption that it will be used by individuals.

image_viewer.py


@app.route('/download_and_face/<item>', methods=['GET', 'POST'])
def download_and_face(item):
    """Download image, face image."""

    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))

in conclusion

--I created a web application that deletes unnecessary face images while comparing the downloaded image with the face image. --I think it's much more convenient than deleting the face image using the Mac Finder. ――It takes some time and getting used to creating a web application. ――Next time, we plan to inflate the face image.

Recommended Posts

Simple management app for download images and face images
An app for smart people and smart people
Upload and download images with falcon
Precautions for handling png and jpg images
Download profile images (icons) for everyone from Slack