|
http://rubinghsoftware.de/projects/alfavision_comp/
|
Projekt | |
Programmiertest component extraction | |
Für | |
Alfavision GmbH & Co. KG (www.alfavision.de), bei Passau | |
Datum | |
Aug-Sep 2011 | |
Platforms | |
ANSI C, gcc, gdb, Linux PC | |
![]() |
|
Diese Seite enthält einen Programmiertest den ich gemacht habe für eine Bewerbung in 2011 bei einer kleinen Firma die sich befasst mit optischer 3D-Vermessung und der dazugehörenden Bildverarbeitung.[1]
Die Aufgabe war, Pixelanzahl, bounding box, und Schwerpunkt zu bestimmen für jede der 4-connected components in einem Binärbild.
Die Aufgabe und meine Lösung (mit Quellcode) sind hier aufgenommen mit freundlicher Genehmigung der Firma Alfavision (www.alfavision.de).
Gegeben: Bilddatentyp wie folgt: typedef struct image_ { int size_x; /* Bildgröße in x-Richtung */ int size_y; /* Bildgröße in y-Richtung */ unsigned char *data; /* Zeiger auf die Bilddaten */ } image_t; Die Bildgröße beträgt size_x mal size_y Bildpunkte (Pixel) der Größe 1 Byte. Die Bilddaten sind zeilenweise, beginnend mit der linken, oberen Ecke abgelegt (auf das letzte Pixel der ersten Zeile folgt im Speicher unmittelbar das erste Pixel der zweiten Zeile). Der Zeiger data zeigt auf das linke, obere Pixel. size_x mal size_y Bytes/Bildpunkte sind zugreifbar. Gegeben: Zusammenhangskomponenten-Datenstruktur wie folgt: typedef struct comp_ { int min_x; /* Kleinste vorkommende x-Koordinate */ int min_y; /* Kleinste vorkommende y-Koordinate */ int max_x; /* Größte vorkommende x-Koordinate */ int max_y; /* Größte vorkommende y-Koordinate */ int pixels; /* Anzahl der Bildpunkte in der Komponente */ double bary_x; /* Schwerpunkt, x-Koordinate */ double bary_y; /* Schwerpunkt, y-Koordinate */ } comp_t; min_x, min_y, max_x und max_y sind die Koordinaten eines umschreibenden Rechtecks (Bounding Box) um die Zusammenhangskomponente, die pixels Bildpunkte enthält. Der geometrische Schwerpunkt der Komponente liegt an der Position bary_x, bary_y. Aufgabe: Entwerfen Sie eine Algorithmik in ANSI-C, die aus einem vorgegebenen Binärbild (die Bildpunkte haben entweder Grauwert 0 oder Grauwert 255). alle Zusammenhangskomponenten extrahiert. Eine Zusammenhangskomponente umfasst alle Pixel mit Grauwert 255, die von einem beliebigen Pixel der Komponente aus über den linken, rechten, oberen oder unteren Nachbarn erreichbar sind. Die zu entwerfende Interface-Routine sollte als Eingabeparameter einen Zeiger auf ein Binärbild enthalten. Für jede Komponente soll die Anzahl der enthaltenen Bildpunkte, das umschreibende Rechteck und der Schwerpunkt erfasst werden. Die Ausgabe der Komponenten kann als Liste, Vektor, Array, Iterator oder in sonstiger geeigneter Art und Weise erfolgen. Entsprechende Parameter oder Rückgabewerte sind in der Interface-Routine vorzusehen. Die Routine(n) sollten zumindest kurz kommentiert sein. Der Gedankengang der Lösung sollte aus der Kommentierung ersichtlich werden. Hilfsdatenstrukturen und Hilfsroutinen nach Bedarf. |
Vorher hatte ich schon einen Algorithmus gekannt fürs Erkennen der 4-connected components
in einem Binärbild, nämlich das sogenannte Connected Components Labeling, und diesen
Algorithmus habe ich als Ausgangspunkt genommen. Der Connected Components Labeling
Algorithmus macht mehrere Durchgänge (passes) durchs image, was notwendig ist damit die labels
vollständig gesetzt werden können.
Jedoch, in dieser Programmieraufgabe ist das vollständige label image nicht als output
notwendig; und ist es möglich gewesen, die unterliegende Idee aus dem Connected Components
Labeling Algorithmus so einzusetzen dass die für diese Aufgabe benötigte Information aus dem
image extrahiert werden kann in nur einem Durchgang (one pass) durch das image.
Meinen Algorithmus habe ich einigermaßen ausgiebig optimiert; an erster Stelle in Laufzeit, und an zweiter Stelle in Speicherbenutzung. Solche algorithmische Optimierung zu engineeren und feinzuschleifen ist eine der Arten von Aufgaben die mich generell stark motiviert, und eine meiner Stärken.
Meinen Algorithmus habe ich ausführlich beschrieben (auf Englisch) in einem großen comment block in einer der source files in der von mir eingesandten Quellcode. Diese Algorithmusbeschreibung aus dem Quellcode, zur besseren lesbarkeit umformattiert von plain ASCII text nach HTML aber sonst ungeändert gelasssen, habe ich zusätzlich hier auf einer separaten Seite hingestellt.
Die Sourcen sind, wie in der Aufgabe verlangt, reines ANSI C. Es ist ein stand-alone Programm, das als seine einzigen externen Abhängigkeiten nur die standard C libraries benutzt die geinterfaced werden durch die header files assert.h, stdlib.h (für malloc() und free()), stdio.h, limits.h, und stdarg.h.
![]() | (Abb. 1) Dependency diagram. A block uses only those other blocks that are located strictly vertically below it. |
processimage.c processimage.h | Algorithmus + Kommentar über den Algorithmus |
lineacc.c lineacc.h lmap.c lmap.h | Datenstrukturen (und Operationen darauf) fur temporäre Daten benutzt in processimage.c |
image.h comp.h | Die Definition von image_t und comp_t aus der Aufgabenbeschreibung |
label.h | typedef unsigned int label_t |
debug.c debug.h | Optionale debug output nach stdout |
testdata.c testdata.h | Test images (definiert als literal data) |
main.c | main(), ruft processimage.c auf auf die test images |
Die Links in der linken Spalte der obigen Tabelle führen zu Darstellungen der source files in
der Form von HTML-Seiten. (So gemacht um viewing der sourcen im internet browser
möglich zu machen.)
Ein gzip-Archiv das alle source files in origineller Form enthält (und mit den originellen
Dateinamen) ist hier.
Entpacken:
gunzip countcomponents.tar.gz, dann tar -xvf countcomponents.tar
Mit dem GNU gcc compiler wird aus den Sourcen der Executable countcomponents wie folgt erzeugt:
gcc -ggdb -Wall -ansi -DDEBUG \ lineacc.c \ lmap.c \ debug.c \ processimage.c \ testdata.c \ main.c \ -o countcomponentsDas unter dem source listing erwähnte gzip-Archiv enthält ein UNIX shell script cl_all.sh das den obigen gcc-Aufruf ausführt.
Das Programm wird auf der Kommandzeile wie folgt aufgerufen (unter UNIX/Linux):
./countcomponents TESTNR
./countcomponents TESTNR | grep OutComp
Das required argument TESTNR, eine integer Zahl in [1,9], spezifiziert welcher der eingebauten Testcases ausgeführt werden sollte.
Das Programm loggt nach stdout mehr oder weniger "alles" das während Programmlauf passiert. Sobald eine Komponente (4-connected component) fertig gefunden ist, werden die Daten dieser Komponente vom Programm ausgedruckt auf einer Zeile die Anfangt mit dem Kennwort "OutComp". In der zweiten Kommandozeile oben werden nur diese Outputzeilen durchgelassen, wodurch übrig bleibt eine schlichte Auflistung aller gefundenen Komponenten (mit deren Pixelzahl, bounding box, und Schwerpunkt).
Testaufgaben über 3D-Sehen aus der gleichen Bewerbung.
|
[1] | Die Bewerbung selbst habe ich letzendlich leider nicht weiterverfolgen können, weil die Firma sich kurz nach Einsendung wegen interner Reorganisation gezwungen gesehen hat, die Bewerbung zeitlich zu verschieben, so dass ich ein anderes Stellenangebot habe annehmen müssen. Die Algorithmenoptimierung zu finden ist aber eine interessantes kleines Projekt gewesen, und ich freue mich, meinen Algorithmus hier veröffentlichen zu können. |