OpenCV Objekterkennung mit Cascade Classifier

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
RCLK
User
Beiträge: 15
Registriert: Donnerstag 17. Oktober 2019, 10:10

Hallo zusammen,

ich habe mir ein PiCar, im folgenden Auto genannt, von Sunfounder zugelegt und es so programmiert, dass es mithilfe von OpenCV von selbst fährt. Nun wollte ich das Programm erweitern, sodass das Auto auch Verkehrsschilder wie ein Stoppschild erkennt. Nun habe ich bissien gegoogelt und bin auf die Cascade Classifier von OpenCV gestoßen. Da es genug Tutorials zu dem Thema gibt habe ich es auch schnell geschafft ein Stoppschild zu erkennen. Leider ist die Performance von dem Auto deutlich schlechter, wenn ich das Auto mit dem classifier starte. Plötzlich werden Linien zu spät erkannt, sodass das Auto aus der Spur fährt und eigentlich macht was er will. Wenn ich den entsprechenden Code vom Classifier dann aber auskommentiere läuft das ganze wieder wunderbar und das Auto fährt auch so wie er soll.

Meine Frage ist nun woran das liegt. Schafft es das Pi nicht gleichzeitig eine Linieinerkennung und eine Opjekterkennung, wie die vom Stoppschild, durchzuführen? Ist er dafür zu schwach oder liegt das eventuell am Code, den ich geschrieben habe? Den Code zum Classifier und die entsprechende Stelle, in der ich auf die Funktion zugreife, ist unten. Gibt es noch andere Methoden, die ich nutzen kann um eine Objekterkennung wie für Stoppschilder zu machen die das Pi auch während der Fahrt schafft?

Ich bin noch ein relativer Anfänger was Python Programmierung angeht, verzeiht mir daher meine groben Programmierkenntnisse.

Schonmal vielen Dank für die Hilfe

Code: Alles auswählen

import cv2
import numpy

def cascade_classifier(frame,classifier):
    stopSign_cascade=cv2.CascadeClassifier('Stopsign.xml')
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    stopSign=classifier.detectMultiScale(gray)

    for (x, y, w, h) in stopSign:
        cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 4)

    return stopSign
    

Hier greife ich darauf zu:

Code: Alles auswählen

def main():
    
    for frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True):
        
        frame= frame.array
        frame = cv2.flip(frame, -1)
        
        processed_frame, steer_dir, offset, roi_frame = frame_processing.frame_processing(frame)
        
        steering_angle_PWM, speed = steering_control.steering_processing(steer_dir, offset)

        stop = cascade.cascade_classifier(frame,stopSign_cascade)

        if len(stop) == 0:
            print ('Stoppschild erkannt...Halte an')
            motor.stop()
            time.sleep(3)
            break
            
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ein grober Fehler den du machst: du erzeugst den classifier für jeden Frame neu. Das ist teuer, und diese Kosten willst du nur einmal zu Beginn deines Programms bezahlen. Und danach immer mit dem existierenden Klassifier arbeiten.

Das es danach wirklich geht ist aber nicht garantiert. Ggf musst du tiefer in die Trickkiste greifen. Zb Regionen im Bild die rote Farbanteile haben erkennen, und nur die erkennen lassen. Denn sonst sucht der das ganzen Bild ab, und auch das ist teuer.
RCLK
User
Beiträge: 15
Registriert: Donnerstag 17. Oktober 2019, 10:10

Erstmal danke für die schnelle Antwort,

den Fehler habe ich jetz beseitigt, habe wohl vergessen die Zeile rauszunehmen^^

ich werde es mal versuchen in dem ich ein Region of Interest kurz ROI zu definieren und den classifier nur in der region suchen zu lassen. Danke für den Tipp. Hoffe das klappt dann einigermaßen. Ich melde mich wieder.
RCLK
User
Beiträge: 15
Registriert: Donnerstag 17. Oktober 2019, 10:10

Hallo zusammen,

Ich habe mir den code nun so umgeschrieben, dass ich nur noch eine Region des Bildes betrachte und das der classifier nur in dieser Region nach dem Stoppschild sucht. Leider tut sich das Auto immernoch schwer beides gleichzeitig zu machen und is so langsam, dass das Auto immer aus der Spur fährt.

Hier nochmal der Code wobei ich nur eine Region des Bildes betrachte.

Code: Alles auswählen

def cascade_classifier(frame,cascade):
    
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    stopSign=cascade.detectMultiScale(gray)

    for (x, y, w, h) in stopSign:
        cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 4)
    return stopSign

def region_of_interest(image):

    vertices = np.array([
        [(390, 80), (460, 80), (460, 130), (390, 130)]
    ])

    mask = np.zeros_like(image)
    cv2.fillPoly(mask,vertices,255)
    masked_image = cv2.bitwise_and(image,mask)
    return masked_image
hier wird es aufgerufen:

Code: Alles auswählen

def main():
    
    for frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True):
        
        frame= frame.array
        frame = cv2.flip(frame, -1)
        
        processed_frame, steer_dir, offset, roi_frame = frame_processing.frame_processing(frame)
        
        steering_angle_PWM, speed = steering_control.steering_processing(steer_dir, offset)
        
        cropped_image = frame_processing.region_of_interest(frame)
        
        stop = frame_processing.cascade_classifier(cropped_image, stopSign)
        
        print stop
        ....
        
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das sieht falsch aus. Du musst schon ein *kleineres* Bild reingeben. Nicht einfach die Region ausmaskieren. Der Classifier weiss doch nicht, dass da schwarz drumrum ist. Sondern sucht sich da ueberall durch.

Ein simples

Code: Alles auswählen

def region_of_interest(image):
    return image[y:y+hoehe,x:x+breite]
sollte reichen.
RCLK
User
Beiträge: 15
Registriert: Donnerstag 17. Oktober 2019, 10:10

Hey super danke für die tolle Hilfe, es klappt jetzt alles so wie ich es wollte. Das Auto fährt nun ohne aus der Spur zu kommen. Das Stoppschild wird ebenfalls erkannt :)

Leider bissien spät geantwortet da ich im Urlaub war.

Da ich das mit dem Stoppschild nun geschafft habe und die andere Verkehrzeichen so ähnlich aufbauen kann, möchte ich noch ein Zebrastreifen einbauen. Also ich möchte das das Auto ein Zebrastreifen erkennt und vorerst immer davor stoppt, genau wie beim Stoppschild. Wenn ich das geschafft habe würde ich das noch so programmieren wollen, dass er nur stoppt wenn am Zebrastreifen eine Person steht in meinem Fall eine Legofigur. Aber vorerst reicht mir das schon wenn das Auto beim Stoppschild direkt anhält.

Mein Zebrastreifen ist in Schwarzen streifen da der Untergrund Weiß ist.

Leider habe ich keine Ahnung wie ich das machen soll. Ich habe mir erst gedacht das das Auto stoppen soll sobald er erkennt, dass mehr schwarze Streifen im Bild sind als sonst. Doch wie gehe ich hier vor? Bei der Spurerkennung habe ich den Treshold Filter benutzt um nur den Schwarzen anteil anzuzeigen und zu erkennen.

Dort sieht es so aus:

Code: Alles auswählen

ret, mask = cv2.threshold(gray, 30,255, cv2.THRESH_BINARY_INV)
Doch ich brauche irgendein Rückgabewert mit dem ich arbeiten kann. Da ich dem Auto sagen muss wann er stoppen soll. Da fehlen mir grad die Kenntnisse und ich weis nicht wie ich weiter machen soll.

Falls einer ne Idee dazu hat höre ich mir die sehr gerne an. Vielen Dank schonmal!
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es ist wie immer bei CV-Fragen nicht einfach bis unmoeglich, sowas ohne Anschauung zu beantworten. Hast du konkrete Bilder von einer normalen Strasse vs. des Zebrastreifens? Das hilft. Ansonsten fallen mir nur hough-lines ein, die du suchen und dann basierend auf deren Anzahl/Ausrichtung/Verteilung etc. einen Klassifikator schreiben kannst.
RCLK
User
Beiträge: 15
Registriert: Donnerstag 17. Oktober 2019, 10:10

Danke für die schnelle Antwort,

an HoughLines habe ich noch nicht gedacht gehabt, das werde ich definitive noch ausprobieren.

Leider kann ich keine Bilder hochladen, oder ich bin einfach zu Blöd dafür :roll:

edit:
habs doch noch hinbekommen :lol:

Also hie meine Straße ohne Zebrastreifen:

Bild

Und hier eine mit Zebrastreifen:

Bild
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Eine Moeglichkeit das zu erkennen waere

- einen Streifen aus der Mitte des Bildes schneiden.
- den mit adaptive Thresholding in schwarz/weiss umwandeln.
- etwas mit weiss drumrum padden.
- eine Kontour-Erkennung machen, und die Nummer der ermittelten Kontouren bestimmen. Wenn das 7 sind, dann ist da der Zebrastreifen.
RCLK
User
Beiträge: 15
Registriert: Donnerstag 17. Oktober 2019, 10:10

hallo deets,

ich habe das so versucht wie du es beschrieben hast, doch leider weis ich nicht genau was du mit padden meinst. Ich habe einige Sachen im Inet gefunden dazu doch ich weis nicht wie ich es in meinem Fall verwenden soll. Wenn du mir nochmal genauer erklären könntest was du mit padding meisnt wäre ich dir sehr dankbar.

Deswegen habe ich es statt mit dem Thresholding mit dem Canny versucht und es klappt eigentlich auch ganz gut. Nur passiert es ziemlich oft das das auto auch an Stellen stoppt wo es gar keine Zebrastreifen gibt. Es liegt daran das ich die Strecke auf drei zusammengeklebten Tapeten aufgebaut habe und die Tapete leider auch etwas geknickt ist. Durch die Knicke werden an manchen stellen viele konturen erkannt was dazu führt das das Auto stehen bleibt. Ich denke mit Thresholding wird es bestimmt besser klappen, doch da komme ich grade nicht weiter.

Auch mit dem HoughLines habe ich es probiert. Aber damit hat es bis jetzt leider am schlechtesten geklappt.

Hier einmal der Code den ich zum Canny Versuch geschrieben habe. Die funktionen rufe ich in der Main nacheinander auf und sage, dass das Auto stoppen soll sobald 14 oder mehr Contoures erkannt werden.

Code: Alles auswählen

def cropped_image(image):
    y = 300
    hoehe = 50
    x= 100
    breite = 500
    return image[y:y+hoehe,x:x+breite]

def canny (frame):
    grey = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
    blur = cv2.GaussianBlur(grey, (5, 5), 0)
    canny = cv2.Canny(blur, 50, 80)
    return canny

def findContours(image):
    contours, hierarchy = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    return len(contours)
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wegen der Knicke habe ich dir das adaptive Thresholding vorgeschlagen. Und "padding" heisst einfach einen Rand einzufuehren. Wenn du Kontouren erkennen willst, dann muessen die ja nun umgeben sein von etwas.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Mit diesem Programm funktioniert es fuer mich ganz gut. Leider findet der auch immer die aeussere Kontour, und das adaptive Thresholding produziert (korrekterweise) Linien. Damit ist fuer mich der Trigger 15 Kontouren. Alternatv kann man das Bild padded mit Houghlines behandeln, und dann auch noch neben der Anzahl die gemeinsame Fluchtrichtung in Betracht ziehen.

Code: Alles auswählen

import sys
import cv2
import numpy as np

REGION_TOP = 0.4
REGION_BOTTOM = 0.5

def main():
    input_image = cv2.imread(sys.argv[1])
    cv2.imshow("original", input_image)
    height, width, *_ = input_image.shape
    part = input_image[int(height * REGION_TOP):int(height * REGION_BOTTOM),:]

    gray = cv2.cvtColor(part, cv2.COLOR_BGR2GRAY)
    thresholded = cv2.adaptiveThreshold(gray, 255, cv2. ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 15, 10)
    cv2.imshow("thresholded", thresholded)
    kernel = np.ones((5,5), np.uint8)
    dilated = cv2.dilate(thresholded, kernel, iterations=1)
    cv2.imshow("dilated", dilated)
    eroded = cv2.erode(dilated, kernel, iterations=2)
    cv2.imshow("eroded", eroded)
    height, width, *_ = eroded.shape
    padded = np.ones((height + 10, width + 10), dtype=np.uint8) * 255
    padded[5:height + 5, 5:width + 5] = eroded
    contours, hierarchy = cv2.findContours(padded, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    padded_color = cv2.cvtColor(padded, cv2.COLOR_GRAY2BGR)
    cv2.drawContours(padded_color, contours, -1, (0,255,0), 3)
    cv2.imshow("padded_color", padded_color)
    print(len(contours))
    cv2.waitKey()


if __name__ == '__main__':
    main()
RCLK
User
Beiträge: 15
Registriert: Donnerstag 17. Oktober 2019, 10:10

Der Code sieht super aus, bei mir funktioniert der auch wunderbar auf dem Bild. Doch bei einer laufenden Aufnahme wird das etwas schwierig, da der Pi da nicht so schnell hinterherkommt. Das heißt das Auto kommt oft aus der Spur da sich die Frames so spät aktualisieren und er die Linien nicht erkennt.

Ich habe da jetzt ne andere Lösung gefunden, da bin ich aber auch nur dank dir drauf gekommen. Ich habe wieder nur ein streifen aus der Mitte des Bildes betrachtet. darauf hab ich das Thresholding angewendet. Danach habe ich den array den der threshhold mir rausgibt aufsummiert. so habe ich meinem auo dann gesagt das er erst stoppen soll, wenn er einen bestimmten Wert an weißen Pixel erkannt hat

Hier nochmal der code dazu vllt. ist es so einfacher zu verstehen.

Code: Alles auswählen

def histogram(mask):
    histogram = np.sum(mask)/1000
    return histogram

def binary_filter(frame):

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

   # ret, mask = cv2.threshold(gray, 160,255, cv2.THRESH_BINARY)                        ## für weiße Linien
    ret, mask = cv2.threshold(gray, 40,255, cv2.THRESH_BINARY_INV)                      ## für schwarze Linien
    #edges = cv2.Canny(mask,200,300)
    #blur = cv2.GaussianBlur(edges, (5,5),0)
    #print (mask)
    return mask

#Abschnitt wo dem auto gesagt wird wann er stoppen soll
    
if len(stop) > 0 or histogram > 4200 :    
            font = cv2.FONT_HERSHEY_SIMPLEX
            cv2.putText(processed_frame, 'STOP', (500, 60), font, 0.8, (255,0,0), 2, cv2.CV_AA)
            motor.setSpeed(0)
            time.sleep(3)
            motor.setSpeed(speed)
            motor.forward()
Da nun Stoppschild und Zebrastreifen erkannt werden, will ich das nur noch optimieren. Ich habe noch ein Problem das das Auto, nachdem es das Stoppschild bzw. das Zebrastreifen erkannt und gestoppt ist, nocheinmal stoppt und nicht direkt nach dem ersten mal stoppen und warten weiter fährt.
Gibt es da etwas was ich verwenden kann, damit das Auto immer nur einmal stoppt wenn er was erkennt? Leider habe ich hierzu nichts gefunden.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn der Klassifikator gut genug ist, dann ist ja fein. Bezueglich der Frage - da "gibt" es nichts, denn das ist dein Programm, das da offensichtlich was falsch macht. OpenCV selbst hat mit der Steuerung eines Autos ja ueberhaupt nichts zu tun, und wie du den Zustand deines Autos definierst und veraenderst - das ist rein deine Sache. Da hast du halt nen Bug bzw. ein Verhalten, das du einprogrammieren musst. So ist es ja zB klar, dass man nach dem erkennen eines Stoppschildes dieses nicht aufhoert zu erkennen, nur weil man mal gestoppt hat. Man muss also wenn man wieder losfahren will bewusst fuer eine kurze Zeit die Information "ich sehe ein stoppschild" ignorieren, denn sonst sieht man das noch ne weile und stoppt/startet/stoppt/... .
Antworten