macbook on a wodden desk

OpenCV Python: eigene Haar Cascade erstellen

Written by

in

Um eine Haar Cascade zu erstellen werden “positive” und “negative” Bilder benötigt. “Positive” Bilder enthalten das Objekt welches gefunden werden soll. Das können entweder Bilder sein, welche ausschließlich das Objekt enthalten oder Bilder, welche (neben anderen Inhalten) das Objekt enthalten, wobei hier die ROI (region of interest) angegeben werden muss. Mit diesen positiven Bildern wird eine Vektordatei erstellt, was im Grunde nichts anderes ist, als alle positiven Bilder zusammen.

Theoretisch ist ein positives Bild und einige tausend negative Bilder ausreichend. Aus dem positiven Bild lassen sich durch verschiedenes “Rauschen” die notwendige Anzahl erstellen. Die negativen Bilder können alles mögliche enthalten (außer das Object selbst).

Als grober Richtwert: das Verhältnis von positiven zu negativen Bildern sollte etwa 2:1 sein.

Pfade für Grafiken:

workspace 
-- pos
-- neg
-- data

Die Grafiken sollten vor dem Training entsprechend verkleinert werden. Positive Grafiken sollten möglichst klein gehalten werden. 50 x 50px sollten für den Anfang ausreichen, diese bringen bereits gute Ergebnisse. Bei den negativen Grafiken verwenden wir 100 x 100px. Je größer die Grafiken, desto länger dauert das Training.

Hier ein einfaches Skript zum Verkleinern der Grafiken resize-images.py:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import cv2
import numpy as np
import glob
import os
import getopt
import sys
from os.path import basename

imgPath = "./"
imgPathOut = ""
resizeDim = 400

usage = '''usage: resize-image.py -i  -o  -d '
    
    -i   : input directory
    -o   : output directory (optional, same as input if empty)
    -d   : dimension of output image (px)
    '''

try:
    opts, args = getopt.getopt(sys.argv[1:],"hi:o:d:",["ipath=","opath=","d="])
except getopt.GetoptError:
    print usage
    sys.exit(2)
for opt, arg in opts:
    if opt == '-h':
        print usage
        sys.exit()
    elif opt in ("-i", "--ipath"):
        imgPath = arg
    elif opt in ("-o", "--opath"):
        imgPathOut = arg
    elif opt in ("-d", "--d"):
        resizeDim = int(arg)

# Check if output path is empty
if not imgPathOut:
    imgPathOut = imgPath

# Check for images
if len(list(glob.iglob('%s/*.jpg' % imgPath))) == 0:
    print 'Could not find any images in path "%s".' % (imgPath)
    sys.exit(2)

# Create output directory if it does not exist
if not os.path.exists(imgPathOut):
    os.makedirs(imgPathOut)

# Loop trough images
for filename in glob.iglob('%s/*.jpg' % imgPath):
    imgName = basename(filename)
    print '%s/%s' % (imgPath, imgName)

    # Read image
    img = cv2.imread(filename)
    # Resize
    img = cv2.resize(img, (resizeDim, resizeDim), interpolation = cv2.INTER_AREA)
    # Convert to grayscale
    img = cv2.cvtColor(img, cv2.COLOR_RGBA2GRAY)
    # Write image
    cv2.imwrite('%s/%s' % (imgPathOut, imgName), img)

Besitzen die Grafiken nun eine geeignete Größe, können wir zum Erstellen der Trainingslisten übergehen. Hier ein Skript zum Erstellen der Trainingsdateien (Datenlisten) create-pos-n-neg.py:

#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import sys
import numpy as np
import cv2

def create_pos_n_neg():

    fpos = open('pos.lst','w')
    fneg = open('neg.lst','w')

    for file_type in ['neg', 'pos']:
        
        for img in os.listdir(file_type):

            if file_type == 'pos':
                try:
                    image = cv2.imread(file_type+'/'+img)
                    h, w, channels = image.shape
                    line = file_type+'/'+img+' 1 0 0 '+str(w)+' '+str(h)+'\n'
                    fpos.write(line)
                except:
                    continue
            elif file_type == 'neg':
                line = file_type+'/'+img+'\n'
                fneg.write(line)

    fpos.close()
    fneg.close()

create_pos_n_neg()

Die Dateien pos.lst und neg.lst enthalten nun die Informationen zu den positiven und negativen Grafiken.

Aus den positiven Grafiken muss nun die Vektordatei erstellt werden, in der alle positiven Grafiken zusammengefasst werden. Dazu wird opencv_createsamples verwendet!

opencv_createsamples -info pos.lst -num 2500 -w 20 -h 20 -vec pos.vec

Nun kann das Training beginnen:

opencv_traincascade -data data -vec pos.vec -bg neg.lst -numPos 2000 -numNeg 1000 -numStages 10 -w 20 -h 20

Dabei wird angegeben, wo die Daten gespeichert werden, wo Vektordatei und Hintergrunddatei ist, wie viele positive und negative Grafiken verwendet werden sollen, die Anzahl der Iterationen, sowie die Breite und Höhe. Beachte, dass weitaus weniger numPos angegeben werden, als eigentlich vorhanden sind! Das ist notwendig, um etwas Raum für die einzelnen Iterationen zu haben.

Es können noch weitere Parameter übergeben werden, aber diese sind vollkommen ausreichend. Die wichtigsten Werte sind die Anzahl der positiven und negativen Grafiken. Es hat sich als praktikabel erwiesen, ein Verhältnis von 2:1 von positiven:negativen Grafiken zu haben (als allgemeine Faustregel). Daraus erhält man nun die “Stufen”, in unserem Fall 10. Es sollten mindestens 10-20 sein. Je mehr Stufen, desto länger dauert es (die Zeitdauer steigt exponentiell). Das Gute ist, man kann anfangs 10 Stufen trainieren und später mit auf 20 gehen. Dabei werden die vorhandenen Stufen verwendet und dort fortgesetzt, wo das letzte Training beendet wurde. Man könnte theoretisch auch mit 100 Stufen beginnen und über Nacht rechnen lassen. Am nächsten Morgen bricht man das Training ab und schaut, wie weit man gekommen ist. Soll der Befehl ausgeführt werden, wenn das Terminal geschlossen ist, dann kann man nohup nutzen:

nohup opencv_traincascade -data data -vec pos.vec -bg neg.lst -numPos 2000 -numNeg 1000 -numStages 10 -w 20 -h 20 &

Hier nun ein einfaches Testskript, um das trainierte Objekt zu erkennen. Dieses nutzt eine Webcam als Bildeingabe, es lässt sich allerdings auch sehr einfach anpassen, um einfache Bilddateien zu verwenden:

test.py:

import numpy as np
import cv2

# this is the cascade we just made. Call what you want
object_cascade = cv2.CascadeClassifier('data/stage.xml')

cap = cv2.VideoCapture(0)

while 1:
    ret, img = cap.read()
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # image, reject levels level weights.
    objects = object_cascade.detectMultiScale(gray, 50, 50)
    
    for (x,y,w,h) in objects:
        cv2.rectangle(img,(x,y),(x+w,y+h),(255,255,0),2)

    cv2.imshow('img',img)
    k = cv2.waitKey(30) & 0xff
    if k == 27:
        break

cap.release()
cv2.destroyAllWindows()

Die Größe der Boxen zum Detektieren der Objekte richtet sich nach den Abmessungen der Trainingsdaten. Bei 50 x 50px ergibt sich also eine relativ kleine Box. Bei größeren Abmessungen wie 100×100 sollte das besser funktionieren, allerdings dauert das Training dann auch deutlich länger.

Originalbeitrag in Englisch: pythonprogramming.net
Bildquelle: unsplash.com


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *