アノテーション確認ツールを作成してみた。 サムネイル編

#背景
アノテーションを行った画像をなるべく少ない労力で確認したい

#既存の確認方法の問題点
アノテーションデータをopenCVで画像に加工してエクスプローラーにあるサムネイルで確認すると楽にはなるが、画像間の空白が広いためにスクロール量が増え、その度に読込が走るため確認速度が思うように上がらない。

***

#解決策
サムネイルを自分で作ればいいじゃん、ということで作ってみました。
予想以上にめんどくさかった・・・・。
コマンドプロンプトで叩けば動きます。
<font color='red'>※画像ごとのアスペクト比は維持されます。</font>

実行方法
　python my_thumbnail.py
入力：
　１．画像フォルダパス
　２．サムネイルにした時の列数
　３．出力フォルダパス
出力：
　１．サムネイル画像　(jpg形式)
　２．画像の横に表示される番号とファイル名の対応リスト　(カンマ区切り形式)

***

#コード

```python:my_thumbnail.py
from enum import Enum
import numpy as np
import cv2
import glob
import os

class MyColor(Enum):
    BLACK  = (0,0,0)
    WHITE  = (255,255,255)

def main():
    concat_img()

def concat_img():
    img_file_list = []
    in_dir         = input("入力フォルダを指定: ") + "/"
    img_array_num  = int(input("画像連結時の列数を指定: "))
    out_dir        = input("出力フォルダを指定: ") + "/"
    ############################
    # 縦連結画像の保存（画像１）
    ############################
    for i in range(img_array_num):
        im_list, fn_list               = collect_image_mod(in_dir+"*.jpg", img_array_num, i)
        tmp_img_name                   = out_dir+str(i)+"_tmp.jpg"
        im_list_resize, img_array_list = vconcat_resize_min(i,list(im_list),list(fn_list))
        img_file_list.append(img_array_list)
        cv2.imwrite(tmp_img_name, im_list_resize)
    ############################
    # 画像の横連結（画像２）
    ############################
    im_list      = collect_image(out_dir+"*.jpg")
    out_img_name = out_dir + "out.jpg"
    img          = hconcat_resize_min(list(im_list))
    cv2.imwrite(out_img_name,img)
    ############################
    # 画像の一覧作成
    ############################
    with open(out_dir+"img_list.txt", 'w') as f:
        for array_list in img_file_list:
            for file in array_list:
                f.write("%s\n" % file)
    ############################
    # 一時画像（画像１）の削除
    ############################
    for i in range(img_array_num):
        tmp_img_name = out_dir+str(i)+"_tmp.jpg"
        os.remove(tmp_img_name)

def collect_image(dir):
    im_list = []
    files = sorted(glob.glob(dir))
    for file in files:
        img = cv2.imread(file)
        assert img is not None, "読み込みに失敗しました"
        im_list.append(img)
    return im_list

def collect_image_mod(dir,mod,cls):
    im_list  = []
    fn_list  = []
    files = sorted(glob.glob(dir))
    for i,file in enumerate(files):
        if i%mod==cls:
            img = cv2.imread(file)
            assert img is not None, "読み込みに失敗しました"
            im_list.append(img)
            fn_list.append(os.path.splitext(os.path.basename(file))[0])
    return im_list, fn_list

def hconcat_resize_min(im_list, interpolation=cv2.INTER_CUBIC):
    h_max = max(im.shape[0] for im in im_list)
    im_list_border = [cv2.copyMakeBorder(im, 1, h_max-im.shape[0], 1, 1, cv2.BORDER_CONSTANT, value=MyColor.BLACK.value)
                      for im in im_list]
    return cv2.hconcat(im_list_border)
    
def vconcat_resize_min(mod_num, im_list, fn_list):
    file_list  = []
    w_min = min(im.shape[1] for im in im_list)
    im_list_resize = [cv2.resize(im, (w_min, int(im.shape[0] * w_min / im.shape[1])), cv2.INTER_CUBIC) for im in im_list]
    text_area_with = len(str(mod_num))*50 + len('-')*50 + len(str(len(im_list_resize)))*50
    for i,img in enumerate(im_list_resize):
        img_num_str = str(mod_num) + '-' + str(i)
        img_num_len = len(img_num_str)
        img = cv2.copyMakeBorder(img, 1, 1, text_area_with, 1, cv2.BORDER_CONSTANT, value=MyColor.BLACK.value)
        img = cv2.putText(img, img_num_str, (0, 50), cv2.FONT_HERSHEY_PLAIN, 4, MyColor.WHITE.value, 4, cv2.LINE_AA)
        im_list_resize[i] = img
        file_list.append(img_num_str + ", " + fn_list[i])
    return cv2.vconcat(im_list_resize), file_list

main()
```

***

#実行例１（列数2で実行）
![out.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451791/aa460954-bac2-ea54-4498-cd5b072c88ed.jpeg)

```text:img_list.txt
0-0, barley-field-1684052__340
0-1, bridge-53769__340
0-2, fantasy-3077928__480
0-3, forest-931706__340
0-4, istockphoto-1054985458-170667a
0-5, istockphoto-1206377870-170667a
0-6, istockphoto-1254013331-170667a
0-7, istockphoto-516449022-170667a
0-8, mountain-landscape-2031539__340
0-9, railroad-163518__340
0-10, road-1072823__480
0-11, sea-164989__340
0-12, sunset-3325080__340
1-0, beach-418742__340
1-1, fantasy-3077928__340
1-2, forest-1072828__340
1-3, hills-615429__340
1-4, istockphoto-1141688597-170667a
1-5, istockphoto-1224783016-170667a
1-6, istockphoto-1254564400-170667a
1-7, istockphoto-525670203-170667a
1-8, mountains-1587287__340
1-9, road-1072821__340
1-10, road-220058__340
1-11, sunset-1373171__340
1-12, tree-736885__340
```

***

#実行例２（列数10で実行）
![out.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451791/8f236d2d-dfec-7559-2a0c-62cd21722834.jpeg)

```text:img_list.txt
0-0, barley-field-1684052__340
0-1, istockphoto-1206377870-170667a
0-2, road-1072823__480
1-0, beach-418742__340
1-1, istockphoto-1224783016-170667a
1-2, road-220058__340
2-0, bridge-53769__340
2-1, istockphoto-1254013331-170667a
2-2, sea-164989__340
3-0, fantasy-3077928__340
3-1, istockphoto-1254564400-170667a
3-2, sunset-1373171__340
4-0, fantasy-3077928__480
4-1, istockphoto-516449022-170667a
4-2, sunset-3325080__340
5-0, forest-1072828__340
5-1, istockphoto-525670203-170667a
5-2, tree-736885__340
6-0, forest-931706__340
6-1, mountain-landscape-2031539__340
7-0, hills-615429__340
7-1, mountains-1587287__340
8-0, istockphoto-1054985458-170667a
8-1, railroad-163518__340
9-0, istockphoto-1141688597-170667a
9-1, road-1072821__340
```

#最後に
単体の画像はpixabayさまよりダウンロードいたしました。
ありがとうございます、感謝しかないです。
https://pixabay.com/ja/