OpenCV・traincascade.exeのカスケード分類器学習用の正解画像のアノテーション作成プログラム(python3.7)

#はじめに
カスケード分類器のアノテーション作成プログラムが配布終わってた？ので自作

#使用方法
正解オブジェクトをクリックしてマウスホイールで領域サイズ変更。比率は自分が使ったときの3:1になっているが適当に変えて使ってください。aで次の画像へ、dで指定したオブジェクト一つ削除、escで終了.
一枚に１つのオブジェクトがあることしか想定してない、増やす場合は一番下のfor文のところを適当に変えればたぶん動く

```python:make_neg_img.py
#
# written by hdnkt　2020/10/26
#

import os
import cv2
import numpy as np


#マウスイベントの処理と四角形領域の保持
class Img_maker:
    sx = 0
    sy = 0
    gx = 0
    gy = 0
    boxes = []
    state = 0#0:押す前　1:押してる途中
    def __init__(self):
        self.sx = 0
        self.sy = 0
        self.gx = 0
        self.gy = 0
        self.boxes = []
        self.state = 0

    #マウスイベントの処理
    def mouse_event(self,event,x,y,flags,param):
        self.gx = x
        self.gy = y

        if event == cv2.EVENT_MOUSEWHEEL:
            if len(self.boxes)>0:
                if flags > 1:
                    self.boxes[len(self.boxes)-1][2]+=12
                    self.boxes[len(self.boxes)-1][3]+=4
                if flags < 0:
                    self.boxes[len(self.boxes)-1][2]-=12
                    self.boxes[len(self.boxes)-1][3]-=4

        if event == cv2.EVENT_LBUTTONDOWN:
            self.sx = x
            self.sy = y
            self.state = 1
        if event == cv2.EVENT_LBUTTONUP:
            self.gx = 120
            self.gy = 40
            self.boxes.append([self.sx,self.sy,self.gx,self.gy])
            self.state = 0
    
    #一番新しい長方形領域削除
    def pop_box(self):
        if len(self.boxes)<=0:
            return
        self.boxes.pop(len(self.boxes)-1)
    
    #指定された点の数
    def size_boxes(self):
        return len(self.boxes)

    #点から領域計算
    def calc(self):
        if len(self.boxes)==2:
            l=self.boxes[1][0]-self.boxes[0][0]
            return l*8//10
        else:
            return 1


    #領域全部教えます
    def get_boxes(self):
        for i in self.boxes:
            yield i
    
    #今何してる？ 0:押し込んでいない　1:押し込み中　
    def get_state(self):
        return self.state

    #今描いてる四角の座標を返すよ　押し込み中しか呼ぶな
    def get_nowRect(self):
        if self.state == 0:
            return
        else:
            return self.sx,self.sy,self.gx,self.gy
        


if __name__ == "__main__":

    pre_path = "#正解画像をいれてあるフォルダのパス#"
    go_path = "#アノテーションファイルを書き出す場所のパス#/annotation.txt"
    subject_num = os.listdir(pre_path)

    #はじめからなら0でいいよ
    print("どこからはじめる")
    start = int(input())

    for i in range(start,len(subject_num)):
        i = subject_num[i]
        #画像を読み込み
        tmp_img = cv2.imread(pre_path+i)
        raw = tmp_img.copy()
        cv2.namedWindow(i)
        #画像メーカーをセットアップ
        img_Maker = Img_maker()
        cv2.setMouseCallback(i,img_Maker.mouse_event)
        #画面に画像を表示
        while 1:
            #お絵かきするためにコピーを用意
            tmp_img=raw.copy()
            if img_Maker.size_boxes()>0:
                for j in img_Maker.get_boxes():
                    cv2.rectangle(tmp_img, (j[0]-j[2]//2,j[1]-j[3]//2),(j[0]+j[2]//2,j[1]+j[3]//2), (255,255,255), thickness=2)
            cv2.imshow(i,tmp_img)

            end = False
            #キー操作
            k = 0
            k = cv2.waitKey(1)
            if k==ord("a"):#次の画像へ
                break
            if k==ord("d"):#一つ前の画像削除
                img_Maker.pop_box()
            if k==27:#結果を保存していったん終了
                end = True
                break
        
        #破壊する。それらは再生できない
        cv2.destroyAllWindows()
        print(i)

        for left in img_Maker.get_boxes():
            print(left)
            #１サンプル分のアノテーション作成

            with open(go_path,mode="a") as f:
                f.write("\n"+"path"+i+" 1 "+str(left[0]-left[2]//2)+" "+str(left[1]-left[3]//2)+" "+str(left[2])+" "+str(left[3]))

            if end:
                break
```