
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