カスケード分類器の不正解画像を正解画像の正解が映っていないところから切り出して生成


初投稿です。ほぼ自分用の備忘録です。

はじめに

 カスケード分類器は画像から対象オブジェクトを検出するためのアルゴリズムです。カスケード分類器の学習時には正解画像とどこに対象オブジェクトがあるのかを指定するファイル、そして対象オブジェクトが映っていない画像(不正解画像)を用意する必要があります。ググるとネットからランダム収集してきた画像を不正解画像として使っている例が多く紹介されています。しかしながらカスケード分類器の「偽陽性率が一定より低くなるまで学習を続ける」という性質を前提として、作ったカスケード分類器を使う場面が限定的である場面を考えると不正解画像のランダム収集は必ずしも得策でない場合があると思います。(必要以上に計算時間がかかる。偽陽性率のノルマを達成しようとするあまり偽陰性率があがってしまう。そもそも不正解画像に正解となるべき画像が紛れ込む(人の顔とかだと結構あるのでは?))
 つまり、入力が限定的であることを想定するなら、用意する不正解もその限定条件内での不正解のみに絞ればよいのではないかという考えのもと、今回紹介するプログラムを書きました。あるいは手っ取り早く有効そうな不正解画像を増やしたいという場面にも使えるのでは。
 私は「人の顔を正面から移した(カメラからの距離などはまちまち)」データセットから、目の検出をするときに使いました。

カスケード分類器とは

ここにカスケード分類器のわかりやすい説明のリンクを貼る

コード

左クリックホールド+離すで長方形領域を指定。
aで次の画像へ
dで最新長方形消去
escでプログラム終了
一応終わるときにどこまで進んだか保存しておくようになってるけど細かい動作は未確認。
欠陥プログラムなのでgo-pathのディレクトリに0 0を書き込んだテキストファイル(ファイル名はlog.txt)を置いておかないと動かない。

make_neg_img.py
#
# written by hdnkt 2020/8/28
#

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_LBUTTONDOWN:
            self.sx = x
            self.sy = y
            self.state = 1
        if event == cv2.EVENT_LBUTTONUP:
            self.gx = x
            self.gy = y
            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 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__":

    #prepathにはもとの画像が入っているフォルダを指定。
    pre_path =
    #gopathには生成した不正解画像を入れるフォルダを指定。
    go_path =
    subject_num = os.listdir(pre_path)

    #カウンターと前回どの画像まで見たかは保存しておきたい。テキストファイルとかに書き出せばいいかな
    counter = 0
    start = 0
    with open(go_path+"log.txt") as f:
        s = f.read()
        counter,start = map(int,s.split())

    for i in range(start,len(subject_num)):
        with open(go_path+"log.txt",mode="w") as f:
            f.write(str(counter)+" "+str(i))

        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()
            for j in img_Maker.get_boxes():
                cv2.rectangle(tmp_img, (j[0],j[1]),(j[2],j[3]), (255,255,255), thickness=2)
            #今描いてる四角も描く
            if img_Maker.get_state()==1:
                sx,sy,gx,gy = img_Maker.get_nowRect()
                cv2.rectangle(tmp_img, (sx,sy),(gx,gy), (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

        #画像の書き出し
        for j in img_Maker.get_boxes():
            counter+=1
            print(go_path+str(counter)+".bmp")
            cv2.imshow("a",raw[j[1]:j[3],j[0]:j[2]])
            cv2.imwrite(go_path+str(counter)+".bmp",raw[j[1]:j[3],j[0]:j[2]])

        #破壊する。
        cv2.destroyAllWindows()

        if end:
            break

まとめ

 カスケード分類器の学習が終わったら結果などまとめて追記する。