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