Ampelphasenerkennung

Aus HSHL Mechatronik
Zur Navigation springen Zur Suche springen
Abb. 1: Darstellung einer erkannten grünen Ampel

Autor: Niklas Lingenauber

Betreuer: Prof. Schneider

Einleitung

Dieser Artikel wurde für die Übung des Kurses Digitale Signal- und Bildverarbeitung verfasst und dokumentiert die Ergebnisse des Projektes "Ampelphasenerkennung". Die allgemeinen Ziele der Projekte können im Wiki-Artikel DSB SoSe2016 eingesehen werden.

Aufgabenstellung

Die Aufgabe lautete eine Algorithmus zu implementieren, der aus einer aufgezeichenten Autofahrt erkennt, ob sich eine Ampel auf den einzelnen Frames befindet und welche Ampelphase diese zeigt. Das Video sollte mit einer an der Front des Autos angebrachten Kamera erstellt werden. Als Entwicklungsumgebung sollte Matlab verwendet werden (Siehe Abb. 1).

Algorithmus

Main-Funktion

Abb. 2: Auswahl eines Videos oder eines Bildes über ein Grafisches User Interface

Die Main-Funktion initialisiert die Ampelphasenerkennung und ruft die einzelnen Funktionen zur Erkennung der Ampelphasen auf. Bei Start dieser Funktion wird nach der einrichtung benötigter Variablen ein User-Interface geöffnet, über das der Anwender ein Video im *.mp4-Format oder ein Bild im *.jpg-Format auswählen kann (siehe Abb. 2). Anschließend wird abhängig von den Maßen des betrachteten Bildes bzw. Frames eines Videos eine Region Of Interest (ROI) definiert. Da von einer starren Kamera in der Fahrzeug-Front ausgegangen wurde, kann ein festgesetzter Anteil im unteren Teil des Bildes vernachlässigt werden, da sich ampeln immer im mittleren bis oberen Segment befinden und so eine Fehlinterpretation von Bremsleuchten verhindert werden kann. Verschiedene Tests haben gezeigt, dass man die besten Ergebnisse beim Betrachten der oberen 5/8 des Bildes erhält.

Nach dem Zuschneiden des Bildes werden die einzelnen Ampelphasen dedektiert. Dazu wurden Funktionen geschrieben die im nächsten Kapitel genauerer erläutert werden. Als Rückgabewert liefern diese die Koordinaten und die Radien von Kreisen, in denen sich vermutlich das Leuchten einer Ampel befindet. Diese Informationen werden für die jeweilige Farbe getrennt gespeichert. Nach der Ausgabe des originalen unbeschnittenen Bildes werden diese Kreise in der jeweiligen Ampelfarbe in das Bild eingezeichnet. Wird eine bestimmte Anzahl an Kreisen je Farbe ermittelt, handelt es sich vermutlich um eine Fehlinterpretation und die Ampeln werden nicht eingezeichnnet. Bei der Bearbeitung eines Videos werden diese Schritte wiederholt für jedes betrachtete Frame durchgeführt. Um die Geschwindigkeit des Algorithmusses zu erhöhen, werden die Frames nur in bestimmten Abständen betrachtet.

Die Main-Funktion lautet wie folgt:

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%          Digitale Signal und Bildverarbeitung          %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Projekt:     Ampelphasenerkennung                      %
%                                                        %
% Autor:       Niklas Lingenauber (2132521)              %
%                                                        %
% Studiengang: Mechatronik 6. Semester                   %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                        %
% Beschreibung: Diese main-Datei initialisiert und       %
%               startet die Ampelphasenerkennung. Sie    %
%               liest eine Datei ein, ruft die nötigen   %
%               Funktionen auf und sorgt für die         %
%               Bildausgabe.                             %
%                                                        %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                        %
% Implementierung: MatLab 2015b                          %
%                                                        %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                        %
% letzte Änderung: 22.06.2016                            %
%                                                        %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

clc;
clear all;

%% Initialisierung %%
%Parameter für ROI
ROIfactor       =   5/8;                    %Faktor, nach dem die Bildhöhe zugeschnitten wird

%Parameter zur Kreissuche
RadiusMin       =   20;                     %Kleinster zu erkennender Kreis
RadiusMax       =   60;                     %größter zu erkennender Kreis
SensityRed      =   0.65;                   %Sensibilität der Kreissuche (Rot)
SensityYellow   =   0.70;                   %Sensibilität der Kreissuche (Gelb)
SensityGreen    =   0.80;                   %Sensibilität der Kreissuche (Grün)

%Parameter für Videoanalyse
Position        =   0;                      %Startposition im Video
StepSize        =   2;                      %Schrittweite im Video zur Beschleunigung des Algorithmus

%Strukturelement zur Dilatatation
SE              =   strel('disk', RadiusMin, 0);   %Kreis mit dem Radius des kleinsten zu erkennenden Kreises

%Strukturelement zur Dilatatation
MaxNum          =   5;                      %Maximale Anzahl Ampeln in einem Bild

%% Datei einlesen %%
%Datei auswählen
[FileName, path]    =   uigetfile({'*.mp4';'*.jpg'},...
    'Bitte wählen Sie ein Bild oder ein Video aus'); 

%Format ermitteln
[~, ~, Format]      =   fileparts(FileName);

%Bild einlesen
if strcmpi(Format,'.jpg')
    img             =   imread([path, FileName]);
    
%Video einlesen
elseif strcmpi(Format,'.mp4')
    video           =   VideoReader([path, FileName]);
    
%Textausgabe bei falschem Datei-Format
else
    error('Unbekanntes Dateiformat. Programm wurde abgebrochen.');
end

%% Analyse eines Bildes %%
if exist('img','var') ==  1
        %Maße des Videos einlesen
       [height, width]  =   size(img(:,:,1));
       
       %ROI bestimmen
       LowmarkROI       =   round(height*ROIfactor);
       imgROI           =   img(1:LowmarkROI,1:width,:);
       
       %Ampelphasen finden
       [centersRed, radiiRed]       =   FilterRed(imgROI, SE, RadiusMin, RadiusMax, SensityRed);       %Rot
       [centersYellow, radiiYellow] =   FilterYellow(imgROI, SE, RadiusMin, RadiusMax, SensityYellow); %Gelb
       [centersGreen, radiiGreen]   =   FilterGreen(imgROI, SE, RadiusMin, RadiusMax, SensityGreen);   %Grün
       
       %Ausgabe
       imshow(img)
       title('Ampelphasen-Erkennung:')
       if length(centersRed)    <=  MaxNum
            viscircles(centersRed, radiiRed,'EdgeColor','r');         %Rotes Ampellicht einzeichnen
       end
       if length(centersYellow) <=  MaxNum
            viscircles(centersYellow, radiiYellow,'EdgeColor','y');   %Gelbes Ampellicht einzeichnen
       end
       if length(centersGreen)  <=  MaxNum
            viscircles(centersGreen, radiiGreen,'EdgeColor','g');     %Grünes Ampellicht einzeichnen
       end
       drawnow;
       
%% Analyse eines Videos %%
else
    while hasFrame(video)
       %Frame einlesen
       img              =   readFrame(video);
       
       %Bild verkleinern zur Laufzeitverbesserung
       img              =   impyramid(img, 'reduce');
       
       %Maße des Videos einlesen
       [height, width]  =   size(img(:,:,1));
       
       %ROI bestimmen
       LowmarkROI       =   round(height*ROIfactor);
       imgROI           =   img(1:LowmarkROI,1:width,:);
       
       %Ampelphasen finden
       [centersRed, radiiRed]       =   FilterRed(imgROI, SE, RadiusMin, RadiusMax, SensityRed);       %Rot
       [centersYellow, radiiYellow] =   FilterYellow(imgROI, SE, RadiusMin, RadiusMax, SensityYellow); %Gelb
       [centersGreen, radiiGreen]   =   FilterGreen(imgROI, SE, RadiusMin, RadiusMax, SensityGreen);   %Grün
       
       %Ausgabe
       imshow(img)
       title('Ampelphasen-Erkennung:')
       if length(centersRed)    <=  MaxNum
            viscircles(centersRed, radiiRed,'EdgeColor','r');         %Rotes Ampellicht einzeichnen
       end
       if length(centersYellow) <=  MaxNum
            viscircles(centersYellow, radiiYellow,'EdgeColor','y');   %Gelbes Ampellicht einzeichnen
       end
       if length(centersGreen)  <=  MaxNum
            viscircles(centersGreen, radiiGreen,'EdgeColor','g');     %Grünes Ampellicht einzeichnen
       end
       drawnow;
       
       %Nächstes Frame bestimmen
       Position             =   Position+StepSize;
       video.CurrentTime    =   Position;   
    end
end

Funktionen zur Ampelphasenerkennung

Abb. 3: Darstellung einer roten Ampel als übergebener ROI, als Filter-Ergbenis und als Endergbenis

Zur Erkennung einer Ampelphase wurden drei Funktiionen erstellt, die die Ampelfarben Rot, Gelb und Grün dedektieren. Als Eingabeparameter benötigen diese ein Bild, ein Strukturelement zur Durchführung einer Dilatation, einen minimalen und einen maximalen Radius der Ampelkreise sowie eine Sensibilität, inwieweit die Formen von einem Kreis abweichen dürfen. Als erstes wird das übergebene Bild nach den Ampelfarben durchsucht. Dafür werden eigens definierte Filter aufgerufen, dessen Entstehung und Aufbau im nächsten Kapitel definiert wurden. Diese liefern ein Binärbild zurück, in dem eine 1 bedeutet, dass die Farbe der gesuchten Ampelfarbe entsprecht. Eine 0 steht für das Gegenteil. Da die Filter relativ scharf eingestellt wurden und nur von einzelnen Pixeln passiert werden, wird anschließend eine Dilatataion mit einem Strukturelement mit der Form eines Kreises durchgeführt und die einzelnen Pixel in große Kreisflächen umgewandelt. In dem entstanden Bild wird anschlißend nach Kreisen innerhalb eines Radien-Intervalls gesucht (siehe Abb. 3). Die Sensibilität legt dabei fest, inwieweit das betrachtete Objekt von der Kreisstruktur abweichen darf. Für jede Ampelfarbe ist ein eigener Filter notwendig.

Die Funktion hat folgenden Aufbau:

function [ centers, radii] = FilterYellow( img, SE, RadiusMin, RadiusMax, Sensity )
%Diese Funktion filtert aus einem Bild die Pixel heraus, die der gelben
%Ampelfarbe entsprechen. Anschließend werden Kreise im gefilterten Bild
%gesucht und zurückgegeben.
%   Übergabeparameter:  img         =   zu analysierendes Bild
%                       SE          =   Strukturelement für Dilatataion
%                       RadiusMin   =   minimaler Radius einer Ampel
%                       RadiusMax   =   maximaler Radius einer Ampel
%                       Sensity     =   Sensibilität der Kreissuche [0 1]
%
%   Letzte Änderung:    22.06.2016
%   Autor:              Niklas Lingenauber

    %gelbe Ampelfarbe finden
    [BW, ~]             =   FindYellow(img);
    
    %Dilatation durchführen
    Dilate              =   imdilate(BW,SE);

    %Helle Kreise auf dunklem Untergrund suchen
    [centers, radii]    =   imfindcircles(Dilate,[RadiusMin RadiusMax],...
        'ObjectPolarity','bright', 'Sensitivity', Sensity);
end

Farbfilter

Abb. 4: Darstellung einer gelben Ampel in verschiedenen Farbräumen mit der App ColorThresholder von Matlab
Abb. 5: Ergebnis einer beispielhaften Filterung nach der gelben Ampelphase mit der App ColorThresholder von Matlab

Zur Filterung der jeweiligen Ampelfarbe wurde mit der von Matlab zur Verfügung gestellten App "Color Thresholder" Bildmasken entwickelt, in denen Pixel mit dem gewünschten Farbwert durch eine 1 markiert und somit weiß dargsetllt werden. Pixel, die diesem Kriterium nicht entsprechen werden als 0 gespeichert und schwarz abgebildet. Aus den definierten Filtern lassen sich anschließend automatisiert Matlab-Funktionen generieren. Der Hintergrund und andere störende Objekte werden somit aus dem Bild entfernt und das daraus entstandene Binärbild kann zur weiteren Bildverarbeitung verwendet werden. Ein großer Vorteil App ist, dass ein gewünschtes Beispielbild in den einzelnen Farbkanälen der Farbräume RGB, HSV, YCbCr und L*A*B* anzeigt werden kann und eine Analyse, welcher Farbraum sich am besten für die Erkennung der Ampelphasen eignet erleichtert wird (siehe Abb. 4). Der HSV-Farbraum stellte sich dabei als am besten geeignet heraus, da sich die Ampelfarben hier am deutlichsten in den einzelnen Kanälen hervorheben. Im ausgewählten Farbraum können anschließend geeignete Schwellwerte für die jeweiligen Farbkanäle ermittelt werden (siehe Abb. 5).Um das bestmögliche Filterergebnis für eine jeweilige Farbe zu erhalten, wurden zur Ermittlung geeigneter Schwellwerte Bild-Collagen erstellt, in denen sich kritische Objekte wie beispielsweise gelbe Verkehrsschilder oder Baustellenbeleuchtungen befinden erstellt. Anhand dieser Collagen wurden verschiedene Schwellwerte getestet und optimiert. Aufgrund der genannten kritischen Objekte wurden die Filter eng definiert, sodass nur vereinzelte Pixel den Filter passieren. Dadurch wird das detektieren von falschen Pixel stark beschränkt, die Fehler-Anfälligkeit im darauffolgenden Programm steigt jedoch stark.

Eine durch die App "ColorThresholder" generierte Matlab-Funktion sieht beispielsweise wie folgt aus:

function [BW,maskedRGBImage] = FindYellow(RGB)
%createMask  Threshold RGB image using auto-generated code from colorThresholder app.
%  [BW,MASKEDRGBIMAGE] = createMask(RGB) thresholds image RGB using
%  auto-generated code from the colorThresholder App. The colorspace and
%  minimum/maximum values for each channel of the colorspace were set in the
%  App and result in a binary mask BW and a composite image maskedRGBImage,
%  which shows the original RGB image values under the mask BW.

% Auto-generated by colorThresholder app on 18-Jun-2016
%------------------------------------------------------


% Convert RGB image to chosen color space
I = rgb2hsv(RGB);

% Define thresholds for channel 1 based on histogram settings
channel1Min = 0.022;
channel1Max = 0.169;

% Define thresholds for channel 2 based on histogram settings
channel2Min = 0.717;
channel2Max = 0.909;

% Define thresholds for channel 3 based on histogram settings
channel3Min = 0.651;
channel3Max = 0.791;

% Create mask based on chosen histogram thresholds
BW = (I(:,:,1) >= channel1Min ) & (I(:,:,1) <= channel1Max) & ...
    (I(:,:,2) >= channel2Min ) & (I(:,:,2) <= channel2Max) & ...
    (I(:,:,3) >= channel3Min ) & (I(:,:,3) <= channel3Max);

% Initialize output masked image based on input image.
maskedRGBImage = RGB;

% Set background pixels where BW is false to zero.
maskedRGBImage(repmat(~BW,[1 1 3])) = 0;

Zusammenfassung

Die Ampelphasen werden erkannt, indem eine Region Of Interest eines einzelenen Bildes bzw. von Frames aus einem Video nach den Ampelfarben Rot, Gelb und Grün gefiltert werden. Die Elemente, die diesen Filter passieren werden durch eine Dilatation vergrößert und auf ihre Form untersucht. Handelt es sich um einen Kreis, wird an dieser Stelle ein Ampel vermutet und in der entsprechenden Farbe eingezeichnet.

Ausblick

Folgende Aspekte sind bei der Ampelphasenerkennung noch weiter zu entwickeln:

  • Filteroptimierung: Das Filtern nach den Ampelfarben ist im Großen und Ganzen noch zu unsicher. Speziell die Farbe Gelb lässt sich im Alltag schwer auf der Basis von einem Farbraum herausfiltern, da sie je nach Perspektive zwischen einem dunklen und schwachen Orange und einem grellen Gelb-Ton variirt. Außerdem ist der Filter momentan tageslichabhängig. Durch die Entwicklung eines Filters, der beispielsweise die Farbe in zwei oder mehr Farbräumen untersucht, könnten die Filter-Ergebnisse verbessert werden.
  • Direkte Kamera-Einbindung: durch das direkte Einbinden einer Kamera könnte man das Programm direkt mit einem Fahrzeug verbinden. Der Aufruf eines Grafischen User Interfaces könnte entfallen und die gewonnenen Informationen beispielsweise von einem Bremsassistenten genutzt werden.
  • Entwicklung als Funktion mir Rückgabewerten: Um einen Einsatz in einem Gesamtsystem zu ermöglichen, muss das Programm als eine Funktion umgeschrieben werden. Defür ist es sinnvoll, den Algorithmus so zu ändern, dass dieser statt des Einzeichnen der einzelnen Ampeln ein Rückgabewert besitzt, der eindeutig eine Ampelphase definiert. Es wären hierfür mindestens 5 Rückgabewerte nötig, die folgende Phasen beschreiben: Rot, Gelb, Grün, Rot-Gelb und keine erkannte Ampelphase.
  • Laufzeit: Die Laufzeit des Algorithmus ist für eine Echtzeitanwendung deutlich zu langsam. Diese könnte durch eine Code-Optimierung oder die Umwandlung in MEX-Files ggf. soweit verbessert werden, dass beispielsweise ein Einsatz im Carolo-Cup oder im Automobilbereich denkbar wäre.

Korrektur/Rückmeldungen

Hier können Nutzer oder kritische Leser (meist Professoren) Verbesserungen fordern/vorschlagen.