Spielfeldmarkierungen

Aus HSHL Mechatronik
Zur Navigation springen Zur Suche springen

Autor: Prof. Schneider

Aufgabe

  • Bestimmen Sie die Ausrichtung des Spielfeldes mit Matlab.
  • Zeichnen Sie die Feldmarkierungen eines Fußballfeldes als Overlay ins Videobild ein.

Tipp: Nutzen Sie die Image Processing Toolbox von Matlab.

Musterlösung

Initialisierung von Matlab

% Comand Window löschen
clc
% Alle Figuren schließen
close all
% Alle Variablen im Workspace löschen
clear all

Bild laden

Laden Sie das Bild des Spielfeldes über einen interaktiven Dialog.

Bild des Spielfeldes mit einer Deckenkamera aufgenommen
% Schalter um das Lade-GUI zu umgehen
bShortCut=true;
 
if (bShortCut==true)
    % alternative Bilddatei laden
    filename = 'Spielfeld_02.png';
    pathname = [cd,'\'];
    disp(['Alternatives Bild laden: ', fullfile(pathname, filename)])
else
    % Interaktiven Dialog starten
    [filename, pathname] = ...
        uigetfile({'*.png';'*.*'},'File Selector'); % Fokus auf Dateiendung '*.png'
    if isequal(filename,0)
        disp('User selected Cancel')  
    else
        disp(['User selected', fullfile(pathname, filename)])
    end
end
% Bild laden
Originalbild = imread([pathname, filename]);

Bild in Graustufen wandeln

Bild in Graustufen Wandeln und die Grüße bestimmen

% Bild in Graustufen wandeln
Grauwertbild = rgb2gray(Originalbild);

% Bildgröße bestimmen
[m,n] = size(Grauwertbild); % z.B. HxB = mxn = XxY 326x485 Pixel^2

Kanten erkennen

% Kantenerkennung
Kantenbild = edge(Grauwertbild,'sobel');
figure
imshow(Kantenbild)
title('Sobel')
Anwendung des Sobel-Operator zur Kantendetektion

Hough Transformation

Geraden zeichnen sich im Hough-Raum als Maxima ab. Das Bild wird somit in den Hough-Raum transformiert und dort analysiert. Aus den Maxima (engl. Peaks) lassen sich die Geraden bestimmen.

% Berechnung der Hough Transformation
[H,theta,rho] = hough(Kantenbild);

% Maxima im Houghraum suchen
P = houghpeaks(H,10,'threshold',ceil(0.4*max(H(:))));

% Linien im Bildraum generieren
lines = houghlines(Kantenbild,theta,rho,P,'FillGap',20,'MinLength',40);


Region of Interest (ROI)

Die wichtigen Linien sind die innerhalb des Bildes. Randlinien werden mit einem einfachen Filter gelöscht.

nElements = 0;

% Randbereich des Bildes ausschließen
xSchwellwertMin = 10;
xSchwellwertMax = n-xSchwellwertMin;
ySchwellwertMin = xSchwellwertMin;
ySchwellwertMax = m-xSchwellwertMin;

% Spielfeldbreite in Pixel aus der Kameraperspektive
Spielfeld.Breite = 161;
Spielfeld.Laenge = 233;

% SpielfeldLänge in Pixel aus der Kameraperspektive

for i=1:length(lines)
    % Randlinien löschen
    if (lines(i).point1(1,1) < xSchwellwertMin) || (lines(i).point1(1,1) > xSchwellwertMax)||...
            (lines(i).point1(1,2) < ySchwellwertMin) || (lines(i).point1(1,2) > ySchwellwertMax)||...
            (lines(i).point2(1,1) < xSchwellwertMin) || (lines(i).point2(1,1) > xSchwellwertMax)||...
            (lines(i).point2(1,2) < ySchwellwertMin) || (lines(i).point2(1,2) > ySchwellwertMax)
        % Element löschen
        lines(i)=[];
    else
        lines(i).Richtungsvektor = [lines(i).point1(1,1)- lines(i).point2(1,1);  lines(i).point1(1,2)- lines(i).point2(1,2)];
        lines(i).n = [-lines(i).Richtungsvektor(2,1); lines(i).Richtungsvektor(1,1)];
        %lines(i).n0 = lines(i).n/norm(lines(i).n)
        if dot(lines(i).point2',lines(i).n)>=0
            lines(i).n0 = lines(i).n/norm(lines(i).n);
        else
            lines(i).n0 = -lines(i).n/norm(lines(i).n); % Normalenvektor weg vom Ursprung
        end;
        lines(i).d=dot(lines(i).n0,lines(i).point1');
    end;
end;

Es ergibt sich das dargestellte Geradenbild.

Geraden nach der Rücktransformation aus dem Hough-Raum

Geranden in Normalenform

Die Geradendefinition in hessescher Nomalenform lautet:

mit

Ortsvektor zu einem beliebigen Punkt der Geraden
Normalenvektor der Geraden
Aufpunktvektor
Abstand der Geraden zum Ursprung

Verifizierung der Geraden

Im nächsten Schritt werden die Geraden gesucht, die den Abmessungen des Spielfeldes entsprechen und dieses einrahmen.

nErgebnis =0;
%% Alle Geraden miteinander vergleichen
% 2Do Brute Force das geht noch geschickter!
% noch einfacher: lines(i).theta vergleichen
for i=1:(length(lines)-1)
    for j=(i+1):length(lines)
        % Geraden in Normalenform
        Skalarprodukt = dot(lines(i).n0,lines(j).n0); % in deg 
        
        if abs(Skalarprodukt) > 0.99 % nahe 1, parallel
            % Winkel zur x-Achse in deg

            
            % Abstand der parallelen Geraden
            AbstandGeraden = dot(lines(j).point1',lines(i).n0)-lines(i).d;
            if abs(abs(AbstandGeraden)-Spielfeld.Breite)<10
                % Längsseite gefunden
                if (lines(i).d < lines(j).d)
                    Geraden.unten = j;
                    Geraden.oben = i;
                else
                    Geraden.unten = i;
                    Geraden.oben = j;
                end
            nErgebnis = nErgebnis+1;
            disp(['Ergebnis: Parallel sind die Geraden ',num2str(i),'->',num2str(j)])
            elseif abs(abs(AbstandGeraden)-Spielfeld.Laenge)<10
            if (lines(i).d < lines(j).d)
                    Geraden.links = i;
                    Geraden.rechts = j;
                else
                    Geraden.rechts = j;
                    Geraden.links = i;
                end    
            end
            nErgebnis = nErgebnis+1;
            disp(['Ergebnis: Parallel sind die Geraden ',num2str(i),'->',num2str(j)])
        end
    end;
end;

Schnittpunkte der Geraden

Die Schnittpunkte der Geraden bilden die Ecken des Spielfeldes. Die Ausrichtung des Spielfeldes ist somit bekannt.

%% Schnittpunkt der Geraden bestimmen
% Ecke oben links
LambdaOL =  (lines(Geraden.oben).d-dot(lines(Geraden.links).point1',lines(Geraden.oben).n0))/dot(lines(Geraden.links).Richtungsvektor,lines(Geraden.oben).n0);
Schnittpunkt.OL = lines(Geraden.links).point1'+ LambdaOL*lines(Geraden.links).Richtungsvektor;

% Ecke oben rechts
LambdaOL =  (lines(Geraden.oben).d-dot(lines(Geraden.rechts).point1',lines(Geraden.oben).n0))/dot(lines(Geraden.rechts).Richtungsvektor,lines(Geraden.oben).n0);
Schnittpunkt.OR = lines(Geraden.rechts).point1'+ LambdaOL*lines(Geraden.rechts).Richtungsvektor;

% Ecke unten links
LambdaOL =  (lines(Geraden.unten).d-dot(lines(Geraden.links).point1',lines(Geraden.unten).n0))/dot(lines(Geraden.links).Richtungsvektor,lines(Geraden.unten).n0);
Schnittpunkt.UL = lines(Geraden.links).point1'+ LambdaOL*lines(Geraden.links).Richtungsvektor;

% Ecke unten rechts
LambdaOL =  (lines(Geraden.unten).d-dot(lines(Geraden.rechts).point1',lines(Geraden.unten).n0))/dot(lines(Geraden.rechts).Richtungsvektor,lines(Geraden.unten).n0);
Schnittpunkt.UR = lines(Geraden.rechts).point1'+ LambdaOL*lines(Geraden.rechts).Richtungsvektor;

%% Winkel berechnen
Winkel = lines(Geraden.links).theta;
disp(['Ausrichtungswinkel des Feldes: ',num2str(Winkel),'°'])

Visualisierung der Ergebnisse

Im letzten Schritt werden die Ergebnisse visualisiert und die Linien eines Fußballfeldes qualitativ auf das Feld projiziert.

%% Torposition ermitteln
%Create a plot that superimposes the lines on the original image.

figure, imshow(Originalbild), hold on
max_len = 0;
for k = 1:length(lines)
   xy = [lines(k).point1; lines(k).point2];
   plot(xy(:,1),xy(:,2),'LineWidth',2,'Color','green');

   % Plot beginnings and ends of lines
   plot(xy(1,1),xy(1,2),'x','LineWidth',2,'Color','yellow');
   plot(xy(2,1),xy(2,2),'x','LineWidth',2,'Color','red');

   % Determine the endpoints of the longest line segment
   len = norm(lines(k).point1 - lines(k).point2);
   if ( len > max_len)
      max_len = len;
      xy_long = xy;
   end
   
   
end

% highlight the longest line segment
plot(xy_long(:,1),xy_long(:,2),'LineWidth',2,'Color','red');

% Ecken zeichnen
plot(Schnittpunkt.OL(1,1),Schnittpunkt.OL(2,1),'oy');
plot(Schnittpunkt.UL(1,1),Schnittpunkt.UL(2,1),'oy');
plot(Schnittpunkt.OR(1,1),Schnittpunkt.OR(2,1),'oy');
plot(Schnittpunkt.UR(1,1),Schnittpunkt.UR(2,1),'oy');

%% Spielfeld berechnen
Mittelpunkt = Schnittpunkt.OL + 0.5*(Schnittpunkt.OR - Schnittpunkt.OL)+0.5*(Schnittpunkt.UL-Schnittpunkt.OL);

%% Neue Figur mit Fußballfeld Overlay
figure, imshow(Originalbild), hold on
plot(Mittelpunkt(1,1),Mittelpunkt(2,1),'w.')

Radius = 40;
rectangle('Position',[Mittelpunkt(1,1)-Radius/2,Mittelpunkt(2,1)-Radius/2,Radius,Radius],'Curvature',[1,1],'LineWidth',2,'EdgeColor',[1,1,1])

% Linie
Mittellinie =[Schnittpunkt.OL + 0.5*(Schnittpunkt.OR - Schnittpunkt.OL),Schnittpunkt.OL + 0.5*(Schnittpunkt.OR - Schnittpunkt.OL)+(Schnittpunkt.UL-Schnittpunkt.OL)];
line(Mittellinie(1,:),Mittellinie(2,:),'LineWidth',2,'Color',[1,1,1])

% Tore
% rectangle('Position',[x,y,w,h])
Hoehe = 50;
Breite = 15;

LinkesTor = Schnittpunkt.OL+0.5*(Schnittpunkt.UL-Schnittpunkt.OL); % Eckposition
%hLinkesTor=rectangle('Position',[LinkesTor',Breite,Hoehe],'LineWidth',2,'EdgeColor',[1,1,1])
x=LinkesTor(1,1);
y=LinkesTor(2,1);
plot(x,y,'yo')
w=Breite;
h=Hoehe;
xv=[x-w/2 x+w/2 x+w/2 x-w/2 x-w/2];yv=[y-h/2 y-h/2 y+h/2 y+h/2 y-h/2];
%h=plot(xv,yv);axis equal;

%rotate angle alpha
R(1,:)=xv;R(2,:)=yv;
%alpha=30*2*pi/360;
alpha=Winkel;
% Translation (Finetuning)
 T(1,1:5)=0
 T(2,1:5)=3
KleinesRechteck=[cosd(alpha) -sind(alpha);sind(alpha) cosd(alpha)]*R+T;
hold on;plot(KleinesRechteck(1,:),KleinesRechteck(2,:),'w','LineWidth',2);

% Zweiter Kasten
w=Breite*2.5;
h=Hoehe*2;
xv=[x x+w x+w x x];yv=[y-h/2 y-h/2 y+h/2 y+h/2 y-h/2];
%h=plot(xv,yv);axis equal;

%% Abstoss
Abstoss = LinkesTor + 2*Breite*lines(Geraden.links).n0;
plot(Abstoss(1,1),Abstoss(2,1),'w.','LineWidth',2)

%rotate angle alpha
R(1,:)=xv;R(2,:)=yv;
% Translation (Finetuning)
 T(1,1:5)=-8
 T(2,1:5)=0
%alpha=30*2*pi/360;
alpha=Winkel;
GrossesRechteck=[cosd(alpha) -sind(alpha);sind(alpha) cosd(alpha)]*R+T;
hold on;plot(GrossesRechteck(1,:),GrossesRechteck(2,:),'w','LineWidth',2);

%% Kreissegment
R=15;
phid=60; % deg
n=0;
for phi=-phid+Winkel:1:phid+Winkel
    n=n+1; % Index
    KreisX(n)=Abstoss(1,1)+R*cosd(phi);
    KreisY(n)=Abstoss(2,1)+R*sind(phi);
end;
plot(KreisX,KreisY,'w-','LineWidth',2)

%% Rechtes Spielfeld
RechtesTor = Schnittpunkt.OR+0.5*(Schnittpunkt.UR-Schnittpunkt.OR); % Eckposition
plot(RechtesTor(1,1),RechtesTor(2,1),'yo')

%% Abstoss
Abstoss = RechtesTor - 2*Breite*lines(Geraden.links).n0;
plot(Abstoss(1,1),Abstoss(2,1),'w.','LineWidth',2)

% Translation (Finetuning)
T(1,1:5)=(Spielfeld.Laenge-Breite)*lines(Geraden.links).n0(1,1)
T(2,1:5)=(Spielfeld.Laenge-Breite)*lines(Geraden.links).n0(2,1)
XY=KleinesRechteck+T;
hold on;plot(XY(1,:),XY(2,:),'w','LineWidth',2);


% Translation (Finetuning)
T(1,1:5)=(Spielfeld.Laenge-w)*lines(Geraden.links).n0(1,1)
T(2,1:5)=(Spielfeld.Laenge-w)*lines(Geraden.links).n0(2,1)

alpha=Winkel;
XY2=GrossesRechteck+T;
hold on;plot(XY2(1,:),XY2(2,:),'w','LineWidth',2);


%% Kreissegment
R=15;
phid=60; % deg
n=0;
for phi=-phid+Winkel+180:1:phid+Winkel+180
    n=n+1; % Index
    KreisX(n)=Abstoss(1,1)+R*cosd(phi);
    KreisY(n)=Abstoss(2,1)+R*sind(phi);
end;
plot(KreisX,KreisY,'w-','LineWidth',2)

Automatische Einblendung der Spielfeldmarkierungen je nach Lage des Spielfeldes

Medien



→ zurück zum Hauptartikel: Bild- und Signalverarbeitung mit MATLAB