Magnus Bjernstad Klas Josephson
Magnus Bjernstad Klas Josephson
Magnus Bjernstad Klas Josephson
Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
Igenkänning av handskrivna siffror med hjälp av<br />
basbilder<br />
Sammanfattning<br />
I den här artikeln visar vi ett exempel p˚a en Matlabimplementation<br />
av identifiering av handskrivna siffror<br />
genom att utnyttja basbilder skapade fr˚an ett<br />
inlärningsmaterial. Dessutom visas en algoritm för<br />
identifiering av hur rader löper i en sida med okända<br />
tecken.<br />
1 Nyckelord<br />
OCR, handskrivna siffror, basbilder, raddetektion<br />
2 Introduktion<br />
Vid m˚anga tillfällen är det intressant att kunna tolka<br />
handskrivna tecken och överföra dessa p˚a textform,<br />
till exempel vid inmatning av text och siffror i handdatorer.<br />
Det finns flera metoder för att konstruera<br />
ett program som utför denna uppgift. Vi har använt<br />
oss av basbilder och jämför sedan de okända objekten<br />
med dessa för att avgöra vilken siffra som tecknet ska<br />
identifieras som.<br />
3 Metod<br />
Segmentering av siffror<br />
Inledningsvis trösklas inbilden s˚a att en binär bild<br />
erh˚alls. Ströpixlar tas bort med hjälp av att morfologiskt<br />
öppna bilden med ett 2 × 2-pixlars strukturelement.<br />
För att koppla samman siffror som best˚ar<br />
av mer än ett sammanhängande omr˚ade dilateras<br />
därefter bilden med ett kvadratiskt strukturelement<br />
med ˚atta pixlars bredd.<br />
Nästa delproblem best˚ar i att fr˚an en bild med siffror<br />
identifiera sammanhängande omr˚aden som vardera<br />
inneh˚aller en siffra. Genom att söka linjärt pixel<br />
för pixel i bilden hittas en punkt där en siffra finns.<br />
Utg˚aende fr˚an denna punkten lokaliseras alla andra<br />
punkter i en 8-sammanhängande väg. För att inte<br />
behöva arbeta vidare med den morfologiskt modifierade<br />
bilden maskas motsvarande pixlar ut fr˚an originalbilden.<br />
<strong>Klas</strong> <strong>Josephson</strong> F-00<br />
E-mail: f00kj@efd.lth.se<br />
<strong>Magnus</strong> <strong>Bjernstad</strong> F-00<br />
E-mail: f00mbj@efd.lth.se<br />
Handledare: Jacob Sternby<br />
För att förenkla sökningen efter nästa siffra raderas<br />
den utsegmenterade siffran fr˚an originalbilden.<br />
P˚a s˚a sätt kan nästa siffra ocks˚a hittas genom linjär<br />
sökning.<br />
Homogenisering av siffror<br />
Att göra de olika utsegmenterade siffrorna s˚a lika som<br />
möjligt torde bidra till ett bättre resultat. Särskilt<br />
med avseende p˚a basbildsmetoden finns det vissa<br />
fördelar med att göra bilderna invarianta under lutning<br />
i originalbilden. Tv˚a ettor som skrivits med olika<br />
lutning rätas därför upp.<br />
Detta kan uppn˚as genom att l˚ata nedre vänstra<br />
hörnet av siffran motsvara origo. För att avgöra hur<br />
mycket siffran ska roteras identifieras masscentrum<br />
varp˚a bilden translateras s˚a att masscentrum hamnar<br />
i origo. Därefter bildas matrisen A med tv˚a kolonner<br />
och med lika m˚anga rader som antal pixlar som är<br />
icke-noll. Varje rad inneh˚aller koordinaterna till motsvarande<br />
pixel. Egenvektorerna till A T A ger d˚a tv˚a<br />
huvudriktningar för bilden med tillhörande egenvektorer.<br />
Bilden vrids s˚a mycket att egenvektorn som hör<br />
till det största egenvärdet blir vertikal.<br />
För att de segmenterade siffrorna ska kunna<br />
jämföras krävs att de är lika stora. Varje inbild skalas<br />
till 30×20 pixlar oberoende om inbilden inte har dessa<br />
proportioner. Resultat av olika skalningsmetoder<br />
ses i figur 1. Om denna skalning inte utförs innan bilden<br />
roteras s˚a riskeras att en felaktig huvudriktning<br />
för platta bilder identifieras.<br />
2 2<br />
2 2<br />
2<br />
2<br />
Figur 1: Skillnaden mellan skalning d˚a proportionerna<br />
bibeh˚alls (vänstra halvan) och d˚a proportionerna inte<br />
bibeh˚alls (högra halvan).
Generering av basbilder<br />
De m stycken bilderna varifr˚an basbilder ska genereras<br />
läses in i en matris<br />
⎛<br />
⎞<br />
x11 x21 . . . xm1<br />
⎜ x12 x22 . . . xm2 ⎟<br />
A ′ ⎜<br />
= ⎜<br />
⎝<br />
.<br />
.<br />
. ..<br />
.<br />
x1n x2n . . . xmn<br />
⎟<br />
⎠ ,<br />
där varje kolumn är en radstaplad bild med n pixlar.<br />
För att kunna generera relevanta basbilder m˚aste alla<br />
inbilder viktas lika, dvs. varje kolonn i A’ ska normeras<br />
enligt<br />
A·i = A′ ·i<br />
||A ′ ·i ||1<br />
.<br />
Kovariansmatrisen C = AA T beskriver variationen<br />
mellan de enskilda pixlarna i bilderna. Egenbilderna<br />
är egentligen egenvektorer till denna kovariansmatris.<br />
Denna är dock av storlek n × n vilket för stora inbilder<br />
är alltför tungt att beräkna numeriskt. Istället<br />
beräknas egenvektorerna till L = A T A som endast<br />
är av storlek m × m - i de flesta fall ett numeriskt<br />
betydligt enklare problem. L˚at V vara en matris med<br />
egenvektorerna till matrisen L i dess kolonner. D˚a kan<br />
m stycken egenvektorer ui till kovariansmatrisen C,<br />
hörande till de m största egenvärdena, beräknas som<br />
en linjärkombination av de radstaplade bilderna i A.<br />
Detta l˚ater sig lätt göras med en matrismultiplikation<br />
U = AV , där kolonnerna i U är egenvektorerna ui.<br />
Efter en normering f˚as de sökta basbilderna.<br />
De mest intressanta basbilderna är de med störst<br />
tillhörande egenvärde. Med hjälp av endast ett f˚atal<br />
av dessa kan bilderna i A approximativt ˚aterskapas<br />
med gott resultat. Huvudsyftet med utnyttjandet av<br />
basbilder är att oidentifierade siffror kan approximeras<br />
med dessa basbilder.<br />
Figur 2 visar fyra basbilder för tv˚aor. Där syns<br />
det att de sista bilderna är betydligt brusigare än de<br />
första.<br />
Figur 2: Basbild nummer ett, tv˚a, 99 och 100 för<br />
tv˚aor.<br />
Identifikation av okända siffror<br />
När väl basbilderna är konstruerade är det enkelt<br />
att avgöra vilken siffra en viss bild inneh˚aller.<br />
Skalärprodukterna mellan en okänd bild och alla basbilderna<br />
för en viss siffra beräknas. Normen av dessa<br />
koordinater blir d˚a ett m˚att p˚a hur lik bilden är denna<br />
siffra.<br />
Raddetektion<br />
För att kunna använda sifferigenkänningen krävs en<br />
presentation av identifikationen som tar hänsyn till<br />
siffrornas inbördes placering. Detta innebär i praktiken<br />
att en rimlig raddetektion m˚aste utföras. Figur<br />
3 visar principen för en s˚adan algoritm. Steg ett<br />
3 4 5<br />
Figur 3: Princip för raddetektion.<br />
är att genom linjär sökning hitta en pixel i den högst<br />
belägna siffran p˚a första raden. Detta motsvaras att<br />
den mittersta siffran i figuren. Utg˚aende fr˚an denna<br />
siffra söks sedan inledningsvis ˚at vänster i dess<br />
förlängning till dess att nästa pixel p˚aträffas. Proceduren<br />
upprepas fram till vänsterkanten p˚a bilden.<br />
Samma sak utförs˚at höger utg˚aende fr˚an startsiffran.<br />
När en siffra är behandlad raderas den fr˚an urbilden<br />
s˚a att den inte p˚aträffas igen.<br />
4 Experiment<br />
Implementation av ett program som identifikation av<br />
handskrivna siffror var den praktiska delen av v˚art<br />
projekt. Programmet är skrivet i Matlab förutom en<br />
beräkningstung funktion skriven i C. Programkoden<br />
är bifogad i appendix A.<br />
Användning av programmet<br />
Programmet kan använda sig av olika användare för<br />
att kunna identifiera siffror skrivna av en viss person<br />
med särskilda särdrag i sin handstil. För att<br />
kunna använda programmet m˚aste inledningsvis basbilder<br />
genereras fr˚an ett inlärningsmaterial. Dessa<br />
inlärningsbilder inneh˚aller vardera endast en typ av<br />
siffra och ska vara i PNG-format med namn enligt<br />
formen img0001.png, img0002.png, . . . . Bilder med<br />
en typ av siffror placeras under motsvarande underkatalog<br />
till respektive användare (se figur 4).<br />
För att skapa basbilder fr˚an inlärningsbilderna<br />
till en viss användare körs kommandot<br />
setupUser(user), där user är en sträng med<br />
användarnamnet. Basbilderna som skapas läggs<br />
i filerna baseImages/user/baseImages0.dat,<br />
baseImages/user/baseImages1.dat, . . . .
|--sourceImages/<br />
| |--user1/<br />
| | |--0/<br />
| | :<br />
| | |--9/<br />
| |--user2/<br />
| |--0/<br />
| :<br />
| |--9/<br />
|--segmentedImages/<br />
| |--user1/<br />
| |--user2/<br />
| :<br />
|--baseImages/<br />
|--user1/<br />
|--user2/<br />
:<br />
Figur 4: Katalogstruktur för programmet.<br />
Att identifiera okända siffror görs med kommandot<br />
identifyImage(image, user, ’plot’). image<br />
anger en sökväg till en bildfil med siffror. Siffrorna<br />
m˚aste vara mörka och bakgrunden ljus. Anges det frivilliga<br />
argumentet ’plot’ visas identifikationen grafiskt<br />
i realtid.<br />
Med programmet bifogas kompilerade versioner av<br />
getContiguous.c för Sun (getContiguous.mexol)<br />
och för Windows (getContiguous.dll). Om<br />
programmet ska köras p˚a en annan plattform<br />
behöver getContiguous.c kompileras med Matlabkommandot<br />
mex getContiguous.c.<br />
Testresultat<br />
I v˚ara testar har vi använt tre olika användare; <strong>Magnus</strong>,<br />
<strong>Klas</strong> och Both. Basbilderna till användaren Both<br />
är genererade fr˚an b˚ade <strong>Magnus</strong> och <strong>Klas</strong> siffror. Tabell<br />
1 och tabell 2 visar hur stor procentandel rätt<br />
som f˚as vid identifiering av cirka 200 siffror av varje<br />
slag.<br />
Resultaten visar att det är en framkomlig väg<br />
att identifiera siffrorna med hjälp av basbilder. Vid<br />
identifikation av användarens egna siffror erhölls en<br />
träffsäkerhet p˚a nästan 100%. Noteras bör att denna<br />
träffsäkerhet knappt sjunker när basbilder som skapats<br />
fr˚an b˚adas siffror används. Intressant vore att<br />
undersöka hur dessa resultat förändras med fler samtidiga<br />
användare.<br />
Att identifiera siffror med basbilder skapade fr˚an en<br />
annan persons siffror ger överlag mycket d˚aligt resultat.<br />
Detta är väntat eftersom det inte krävs särskilt<br />
stor förändring av en handstil för att basbildernas<br />
mest signifikanta delar ska förskjutas till omr˚aden<br />
med tidigare mycket l˚ag signifikans.<br />
Raddetektionen ger i allmänhet korrekta resultat.<br />
Även kraftigt lutande rader hanteras väl. I vissa extremfall<br />
kan ordningen p˚a raderna kastas om.<br />
<strong>Magnus</strong> <strong>Klas</strong> Both<br />
0 100 86 100<br />
1 100 31 100<br />
2 99 38 96<br />
3 100 38 96<br />
4 97 6 96<br />
5 100 89 100<br />
6 99 100 100<br />
7 95 21 98<br />
8 98 92 98<br />
9 97 41 99<br />
Totalt 99 54 98<br />
Tabell 1: Procentandel rätt vid test p˚a <strong>Magnus</strong> siffror<br />
med basbilder genererade med de tre användarna<br />
<strong>Magnus</strong>, <strong>Klas</strong> och Both, där Both använder b˚adas<br />
inlärningsbilder.<br />
<strong>Klas</strong> <strong>Magnus</strong> Both<br />
0 100 100 100<br />
1 100 1 100<br />
2 99 58 97<br />
3 98 81 98<br />
4 91 0 89<br />
5 99 86 100<br />
6 98 97 98<br />
7 100 0 96<br />
8 98 92 99<br />
9 98 38 94<br />
Totalt 98 55 97<br />
Tabell 2: Procentandel rätt vid test p˚a <strong>Klas</strong> siffror<br />
med basbilder genererade med de tre användarna<br />
<strong>Klas</strong>, <strong>Magnus</strong> och Both, där Both använder b˚adas<br />
inlärningsbilder.<br />
Möjliga förbättringar<br />
Önskvärt vore att ha en metod för att avgöra om ett<br />
tecken inte är en siffra. Det enda vi använder oss av<br />
för att urskilja felaktiga tecken är en storlekskontroll<br />
för att hindra alltför sm˚a omr˚aden att tolkas som siffror.<br />
En möjligt förbättring vad gäller klassificeringen är<br />
att utveckla en mer sofistikerad metod för att analysera<br />
de koordinater som f˚ar efter skalärprodukt mellan<br />
den okända bilden och basbilderna. I nuläget avgörs<br />
vilken siffra det är av normen av koordinaterna. En<br />
utveckling av detta är att även ta hänsyn till hur<br />
koordinaterna varierar mellan de olika möjliga siffrorna.<br />
Vidare skulle man kunna göra noggrannare<br />
undersökningar p˚a siffror som ofta ger resultat nära<br />
varandra, främst fyror och nior. Detta skulle kunna<br />
ske genom att man även tittar p˚a specifika särdrag för<br />
siffror. Ett s˚adant exempel är att studera hur m˚anga<br />
h˚al en siffra har. Där skulle fyror i normalfallet ge noll<br />
h˚al, nior ge ett h˚al och ˚attor ge tv˚a h˚al.<br />
Om man skulle använda sig av flera metoder är en
idé att vikta dessa vid klassificeringen s˚a att metoder<br />
som är bra p˚a att till exempel skilja mellan tv˚a<br />
specifika siffror endast har signifikans i detta fall.<br />
Sammanfattning<br />
Vi har i denna rapport gett ett exempel p˚a hur en<br />
implementation av identifiering av handskrivna siffror<br />
kan se ut. V˚ara resultat visar p˚a goda resultat<br />
när basbildsmetoden används. Bra resultat erh˚alls<br />
även när antalet personer som basbilderna bygger p˚a<br />
utökas till tv˚a. Hur detta utvecklas med ännu fler<br />
personer är n˚agot som skulle vara intressant att undersöka.<br />
Speciellt hur bra det d˚a skulle fungera p˚a en<br />
person som inte varit med och genererat basbilderna.
A m-filer<br />
setupUser<br />
function utbild = setupUser(user)<br />
% Segmentera alla bilder som ligger i katalogen<br />
% sourceImages/{user}/[0:9] samt skapa basbilder<br />
% fr˚an dessa.<br />
t = cputime;<br />
for number=0:9<br />
% Segmentera ut siffrorna<br />
saveSegmentedImages(user , number);<br />
% Spara motsvarande basbilder<br />
saveBaseImages(user, number);<br />
end<br />
disp(sprintf(’Total tid: %.2f s’, cputime-t));<br />
saveSegmentedImages<br />
function saveSegmentedImages(user, number)<br />
% Segmentera ut siffrorna som finns i bilderna som ligger i<br />
% katalogen sourceImages/{user}/{number}/. Bilderna sparas i filen<br />
% ’segmentedImages/{user}/segmentedImages{number}.dat’<br />
% Sätt filnamn<br />
imageName = sprintf(’sourceImages/%s/%d/img0001.png’, user, number);<br />
imageNumber = 1;<br />
% Öppna fil för lagring av segmenterade bilder<br />
fid = fopen(sprintf(’segmentedImages/%s/segmentedImages%d.dat’, ...<br />
user, number), ’w’);<br />
% Skriv storleken p˚a varje segmenterad bild<br />
fwrite(fid, [30, 20], ’ubit8’);<br />
fclose(fid);<br />
fid = fopen(sprintf(’segmentedImages/%s/segmentedImages%d.dat’, ...<br />
user, number), ’a’);<br />
% G˚ar igenom alla bilder som tillhör ’user’<br />
while (exist(imageName))<br />
originalImage = imread(imageName);<br />
% Tröskla<br />
originalImage = originalImage < 200;<br />
% Ta bort sm˚a skräp samt dilatera ihop siffror<br />
currentImage = imopen(originalImage, ones(2));<br />
currentImage = imdilate(currentImage, ones(8));<br />
% Koordinater för var nästa bild att extrahera finns<br />
imagePosition = findFirst(currentImage, [1 1]);<br />
% Extrahera denna bilden och uppdatera ursprungsbilden<br />
[foundDigitImage, currentImage] = ...<br />
getImage(currentImage, imagePosition, originalImage);<br />
% G˚a igenom alla siffror i nuvarande bild<br />
while imagePosition ~= -1<br />
% Spara aktuell bild till fil om bilden är tillräckligt stor<br />
if (foundDigitImage ~= -1)
fwrite(fid, foundDigitImage(:), ’ubit1’);<br />
end<br />
% Hitta första icke-nollpositionen i bilden<br />
imagePosition = findFirst(currentImage, imagePosition);<br />
% Segmentera ny siffra om det finns n˚agon<br />
if (isempty(imagePosition) == 0)<br />
[foundDigitImage, currentImage] = ...<br />
getImage(currentImage, imagePosition, originalImage);<br />
end<br />
end<br />
% Sätt namn p˚a nästa fil<br />
imageNumber = imageNumber + 1;<br />
imageName = ...<br />
sprintf(’sourceImages/%s/%d/img%0.4d.png’, user, number, imageNumber);<br />
end<br />
% Stäng filen för lagring av segmenterade siffror<br />
fclose(fid);<br />
disp(sprintf(’Segmenteringen för %d:orna klara’, number));<br />
function updatedPosition = findFirst(inPic, currentPosition)<br />
% Hittar index [row, col] för första elementet i ’inPic’<br />
% vars värde är icke-noll. Returnerar en tom matris om<br />
% alla element är noll.<br />
[nRows nCols] = size(inPic);<br />
for i = currentPosition(1):nRows<br />
for j = 1:nCols<br />
if (inPic(i,j) ~= 0)<br />
updatedPosition = [i j];<br />
return;<br />
end<br />
end<br />
end<br />
updatedPosition = [];<br />
saveBaseImages<br />
function saveBaseImages(user, number)<br />
% Skapa basbilder för siffra ’number’ till de segmenterade<br />
% siffrorna som tillhör ’user’.<br />
% Läs in bilderna som basbilderna ska baseras p˚a. Varje bild lagras<br />
% som en (radstaplad) kolonnvektor i matrisen A.<br />
fid = fopen(sprintf(’segmentedImages/%s/segmentedImages%d.dat’, ...<br />
user, number), ’r’);<br />
% Konrollera om bilder finns<br />
if fid == -1<br />
disp(’Inga segmenterade siffror finns’)<br />
return<br />
end<br />
imageSize = fread(fid, 2, ’ubit8’);<br />
A = fread(fid, [prod(imageSize), inf], ’ubit1’);<br />
fclose(fid);<br />
% Normera kolonnerna i A i 1-normen<br />
A = A ./ repmat(sum(A), size(A, 1), 1);
% Att hitta egenvektorer till kovariansmatrise C=A*A’ är f˚ar sv˚art<br />
% -> studera istället egenvektorer till A’*A (som bara är av<br />
% storlek nPictures*nPictures).<br />
L = A’*A;<br />
[V, eigValues] = eig(L);<br />
% Gör egenvärdena till en vektor istället för en matris<br />
eigValues = diag(eigValues);<br />
% Egenvektorerna U till C kan nu bildas som en linjärkombination av<br />
% bilderna i kolonnerna hos A<br />
U = A*V;<br />
% Sortera egenvektorerna efter storleken p˚a motsvarande egenvärde<br />
[sortedEig eigOrder] = sort(eigValues);<br />
eigOrder = flipud(eigOrder);<br />
U=U(:,eigOrder);<br />
% Normera egenvektorerna<br />
U = U ./ repmat(sqrt(sum(U.^2)), size(U, 1), 1);<br />
% Spara basbilderna.<br />
% Basbilderna sparas i en fil där de första tv˚a talen anger<br />
% storleken p˚a varje bild.<br />
fid = fopen(sprintf(’baseImages/%s/baseImages%d.dat’, user, number), ...<br />
’w’, ’ieee-be’);<br />
% Skriv storleken p˚a varje basbild<br />
fwrite(fid, imageSize, ’ubit8’);<br />
% Skriv basbilderna och stäng filen<br />
fwrite(fid, U(:), ’float32’);<br />
fclose(fid);<br />
getImage<br />
function [digit, updatedImage, digitRect] = getImage(inPic, startPos, originalpic)<br />
% Hittar sammanhängande bild i ’inPic’ med början i ’startPos’.<br />
% Bilden lagras i ’digit’ och den funna siffran ’raderas’ i<br />
% ursprungsbilden och den nya, uppdaterade bilden sparas i<br />
% ’updatedImage’. ’digit’ blir -1 om en för liten bild hittats.<br />
% Extrahera pixlar i ett 8-sammanhängande omr˚ade kring ’startPos’.<br />
% ’updatedImage’ är en bild med detta omr˚ade satt till noll.<br />
% ’digit’ är en {antal pixlar}x2-vektor som inneh˚aller koordinater<br />
% för pixlarna som utgör siffran.<br />
[updatedImage, digit] = getContiguous(inPic, startPos(1), startPos(2));<br />
% Konvertera listan över ing˚aende pixlar till en sparse-matris<br />
digit = sparse(double(digit(:,1)), double(digit(:,2)), 1);<br />
% Plocka ut motsvarande pixlar ur originalbilden<br />
[row,col]=find(digit);<br />
orgDigit=originalpic(min(row):max(row), min(col):max(col));<br />
dilatDigit=digit(min(row):max(row), min(col):max(col));<br />
digit = full(orgDigit & dilatDigit);<br />
% Spara koordinatinformation om den funna bilden<br />
digitRect = [min(row) max(row) min(col) max(col)];<br />
% Skala om bilden till 30x20 om den är tillräckligt stor
if (max(size(digit)) > 20)<br />
digit = resizeDigitImage(digit);<br />
else<br />
digit = -1;<br />
return<br />
end<br />
% Rotera bilden s˚a att egenriktningen pekar rakt upp˚at<br />
digit = rotateImageAlongEigenDirection(digit);<br />
% Ändra storlek igen till 30x20 pixlar<br />
digit = resizeDigitImage(digit);<br />
function outPic = resizeDigitImage(inPic)<br />
% Skala om ’inPic’ s˚a att den täcker 30x20 pixlar. Ta inte hänsyn<br />
% till bildförh˚allandet.<br />
outPicSize = [30 20];<br />
[row col] = find(inPic);<br />
digitRect = [min(row) max(row) min(col) max(col)];<br />
inPic = inPic(digitRect(1):digitRect(2), digitRect(3):digitRect(4));<br />
outPic = imresize(inPic, outPicSize, ’nearest’);<br />
function outPic = rotateImageAlongEigenDirection(inPic)<br />
% Rotera bilden ’inPic’ s˚a att huvudriktningen g˚ar vertikalt.<br />
% Skifta origo till masscentrum av bilden<br />
[nRows nCols] = size(inPic);<br />
[ARows ACols] = find(inPic);<br />
centerOM = centerOfMass(inPic);<br />
ARows = ARows - centerOM(1);<br />
ACols = ACols - centerOM(2);<br />
A = [ARows ACols];<br />
% Beräkna egenvektorerna och egenvärdena. Plocka ut egenvektorn<br />
% hörande till det största egenvärdet.<br />
[eigVec eigValue] = eig(A’*A);<br />
[maxEigenValue maxIndex] = max(diag(eigValue));<br />
eigVec = eigVec(:,maxIndex);<br />
% Räkna ut vilken vinkel bilden ska vridas<br />
if (abs(eigVec(1)) < eps)<br />
theta = pi/2;<br />
else<br />
theta = atan(eigVec(2)/eigVec(1));<br />
end<br />
% Rotera bilden<br />
outPic = imrotate(inPic, -rad2deg(theta));<br />
function centerOfMass = centerOfMass(inPic)<br />
[M, N] = size(inPic);<br />
weightX = sum(inPic, 1);
weightY = sum(inPic, 2);<br />
meanX = weightX *(1:N)’/sum(weightX);<br />
meanY = weightY’*(1:M)’/sum(weightY);<br />
centerOfMass = [meanY meanX];<br />
getContiguous<br />
#include <br />
#include "mex.h"<br />
#include "matrix.h"<br />
struct list {<br />
int row;<br />
int col;<br />
struct list *next;<br />
struct list *prev;<br />
};<br />
typedef struct list item;<br />
mxArray *getContiguous(mxLogical *image, int nRows, int nCols, int startRow, int startCol) {<br />
/* Variabeldeklarationer */<br />
item *pixel, *pixelHead;<br />
item *toCheck, *toCheckHead, *toCheckTail;<br />
int currRow, currCol;<br />
int i,j, nPixels;<br />
mxArray *pixels;<br />
int pixelsDim[2];<br />
int *data;<br />
/* Initiera den nuvarande positionen till startpositionen */<br />
currRow = startRow;<br />
currCol = startCol;<br />
nPixels = 0;<br />
pixelHead = NULL;<br />
toCheck = (item *)mxCalloc(1, sizeof(item));<br />
toCheck->row = startRow;<br />
toCheck->col = startCol;<br />
toCheck->next = NULL;<br />
toCheck->prev = NULL;<br />
toCheckHead = toCheck;<br />
toCheckTail = toCheck;<br />
while(toCheckTail) {<br />
/* Utg˚a fr˚an första pixeln i listan över pixlar att g˚a igenom */<br />
currRow = toCheckTail->row;<br />
currCol = toCheckTail->col;<br />
/* Stega igenom alla grannar till den nuvarande positionen */<br />
for (i = currCol-1; i = 0 && i < nCols && image[nRows*i + j] == 1) {<br />
/* ...gör den svart,... */<br />
image[nRows*i + j] = 0;
}<br />
}<br />
/* ...spara pixelpositionen för den vita pixeln... */<br />
pixel = (item *)mxCalloc(1, sizeof(item));<br />
pixel->row = j;<br />
pixel->col = i;<br />
pixel->next = pixelHead;<br />
pixel->prev = NULL;<br />
pixelHead = pixel;<br />
nPixels++;<br />
/* ...samt spara den nya pixeln som kand. för ytterligare utvidgning. */<br />
toCheck = (item *)mxCalloc(1, sizeof(item));<br />
toCheck->row = j;<br />
toCheck->col = i;<br />
toCheck->next = NULL;<br />
toCheck->prev = toCheckHead;<br />
toCheckHead->next = toCheck;<br />
toCheckHead = toCheck;<br />
}<br />
}<br />
/* Ta bort den precis kontrollerade<br />
pixeln fr˚an kandidater till ytterligare utvidgning */<br />
toCheckTail = toCheckTail->next;<br />
}<br />
pixelsDim[0] = nPixels;<br />
pixelsDim[1] = 2;<br />
pixels = mxCreateNumericArray(2, pixelsDim, mxINT32_CLASS, mxREAL);<br />
data = mxCalloc(nPixels*2, sizeof(int));<br />
i = 0;<br />
pixel = pixelHead;<br />
while (pixel) {<br />
data[i] = pixel->row + 1;<br />
data[nPixels + i] = pixel->col + 1;<br />
pixel = pixel->next;<br />
i++;<br />
}<br />
mxSetData(pixels, data);<br />
return pixels;<br />
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {<br />
unsigned int *pixelList;<br />
mxLogical *image;<br />
mxLogical *updatedImage;<br />
int i,n;<br />
double *startRow, *startCol;<br />
image = mxGetLogicals(prhs[0]);<br />
startRow = mxGetPr(prhs[1]);<br />
startCol = mxGetPr(prhs[2]);
}<br />
/* Skapa en array som inneh˚aller en uppdaterad bild med den<br />
utsegmenterade delen raderad */<br />
plhs[0] = mxCreateNumericArray(mxGetNumberOfDimensions(prhs[0]),<br />
mxGetDimensions(prhs[0]),<br />
mxLOGICAL_CLASS, mxREAL);<br />
updatedImage = mxGetData(plhs[0]);<br />
/* Konvertera alla element i inbilden till heltal */<br />
n = mxGetNumberOfElements(prhs[0]);<br />
for (i=0; i < n; i++) {<br />
updatedImage[i] = (bool) image[i];<br />
}<br />
plhs[1] =<br />
(mxArray *) getContiguous(updatedImage,<br />
mxGetM(prhs[0]),<br />
mxGetN(prhs[0]),<br />
(int)*startRow,<br />
(int)*startCol);<br />
identifyImage<br />
function result = identifyImage(file, user, doPlot)<br />
% Identifierar siffror i bilden ’file’ med basbilder som tillhör<br />
% användaren ’user’. Om ’doPlot’ sätts till ’plot’ f˚as en bild där<br />
% identifikationen överlagras p˚a bilden ’file’.<br />
% Sätt doPlot till false ifall den ej initieras vid anrop<br />
if (nargin < 3)<br />
doPlot = false;<br />
end<br />
% Kontrollera att användaren finns<br />
if (exist(sprintf(’baseImages/%s/baseImages0.dat’, user)) == false)<br />
disp(’Användaren finns inte’);<br />
return;<br />
end<br />
% Intitiera nödvändiga variabler<br />
result = ’’;<br />
digitRect = [1 1 1 1];<br />
% Kotrollera om filen existerar<br />
if (exist(file) == false)<br />
disp(’Filen ej funnen’)<br />
return;<br />
end<br />
% Läser in bild<br />
originalImage = imread(file);
% Konvertera till gr˚askala<br />
if (size(originalImage, 3) == 3)<br />
originalImage = rgb2gray(originalImage);<br />
end<br />
% Tröskla, öppna, dilatera<br />
originalImage = originalImage < 200;<br />
currentImage = imopen(originalImage, ones(2));<br />
currentImage = imdilate(currentImage, ones(8));<br />
% Plotta bilden om det är valt<br />
if (doPlot == ’plot’)<br />
close;<br />
imshow(not(originalImage));<br />
pause(0.1);<br />
hold on;<br />
end<br />
% Koordinater för var nästa bild att extrahera ’börjar’<br />
imagePosition = findFirst(currentImage);<br />
% Stega igenom inbilden tills inga fler rader p˚aträffas<br />
while (imagePosition ~= -1)<br />
% Initiera raden med identifierade siffror<br />
identifiedRow = [];<br />
% Stega ˚at vänster<br />
direction = -1;<br />
% G˚a igenom en rad i ursprungsbilden<br />
while (imagePosition ~= -1)<br />
% Extrahera siffran<br />
[foundDigitImage, currentImage, newDigitRect] = ...<br />
getImage(currentImage, imagePosition, originalImage);<br />
% Behandla bara bilder som är tillräckligt stora<br />
if (foundDigitImage ~= -1)<br />
digitRect = newDigitRect;<br />
% Spara första identifierade siffran i raden<br />
if (isempty(identifiedRow))<br />
rowStartRect = digitRect;<br />
end<br />
% Normalisera intensiteten<br />
foundDigitImage = foundDigitImage/sum(foundDigitImage(:));<br />
% Identifiera siffran<br />
idNumber = identifyImageByBaseImages(foundDigitImage, user);<br />
% Plotta identifikationen om det valts<br />
if (doPlot == ’plot’)<br />
text(imagePosition(2),imagePosition(1),num2str(idNumber), ...<br />
’color’,’k’, ’backgroundcolor’, ’c’);<br />
pause(0.0001)<br />
end<br />
% Uppdatera raden med identifierade siffror
end<br />
if (direction == -1)<br />
identifiedRow = [idNumber identifiedRow];<br />
else<br />
identifiedRow = [identifiedRow idNumber];<br />
end<br />
end<br />
% Leta upp nästa siffra i raden<br />
imagePosition = findNextInRow(currentImage, digitRect, direction);<br />
% Om det inte finns fler siffror ˚at vänster - ändra<br />
% sökriktning<br />
if (isequal(imagePosition, -1) && direction == -1 && ...<br />
isequal(foundDigitImage,-1) == false)<br />
direction = 1;<br />
imagePosition = findNextInRow(currentImage, rowStartRect, direction);<br />
end<br />
end<br />
% Spara den identifierade raden (om den inte är tom)<br />
if (isempty(identifiedRow) == false)<br />
result = sprintf(’%s%s\n’, result, num2str(identifiedRow));<br />
end<br />
% Hitta nästa siffra (som nu är p˚a en ny rad)<br />
imagePosition = findFirst(currentImage);<br />
function firstIndex = findFirst(inPic)<br />
% Hittar index [row, col] för första elementet i ’inPic’<br />
% vars värde är icke-noll. Returnerar en tom matris om<br />
% alla element är noll.<br />
nRows = size(inPic,2);<br />
% Hitta första icke-noll elementet. Linjärt index.<br />
firstIndex = min(find(inPic’));<br />
% Konvertera linjärt index till [row col]-format<br />
firstIndex = [ceil(firstIndex/nRows) mod(firstIndex,nRows)];<br />
function updatedPosition = findNextInRow(inPic, digitRect, direction)<br />
% Söker i riktningen ’direction’ efter nästa pixel som är<br />
% icke-noll. Index för denna pixel lagras i ’updatedPosition’.<br />
% direction == 1 => höger<br />
% direction == -1 => vänster<br />
% Kontrollera riktning<br />
if (direction == 1)<br />
col = digitRect(4);<br />
else<br />
col = digitRect(3);<br />
end<br />
% Sök efter nästa till man n˚att kanten p˚a bilden<br />
while (sum(inPic(digitRect(1):digitRect(2), col)) == 0)<br />
col = col + direction;
% Kolla om vi g˚att utanför bilden<br />
if (col == 0 || col > size(inPic, 2))<br />
% Reurnera -1 d˚a raden är slut<br />
updatedPosition = -1;<br />
return<br />
end<br />
end<br />
% Returnera position för nästa siffra<br />
updatedPosition = ...<br />
[min(find(inPic(digitRect(1):digitRect(2), col))) + digitRect(1) - 1 ...<br />
col];<br />
identifyImageByBaseImages<br />
function idNumber = identifyImageByBaseImages(inPic, user)<br />
% Avgör vilken siffra bilden ’inPic’ inneh˚aller baserat p˚a<br />
% jämförelse med basbilder tillhörande användaren ’user’.<br />
maxNorm = 0;<br />
nUsedBaseImages = 20;<br />
for number = 0:9;<br />
% Läs in basbilderna<br />
fid = fopen(sprintf(’baseImages/%s/baseImages%d.dat’, user, number), ...<br />
’r’, ’ieee-be’);<br />
% Kontrollera om basbilder finns<br />
if fid == -1<br />
disp(’Basbilder saknas’);<br />
return<br />
end<br />
baseImageSize = fread(fid, 2, ’ubit8’);<br />
baseImages = fread(fid, [prod(baseImageSize), nUsedBaseImages], ’float32’);<br />
fclose(fid);<br />
% Radstapla<br />
currentImage = inPic(:);<br />
% Beräkna skalärprodukterna mellan currentImage och alla<br />
% basebilderna för en given siffra.<br />
coordinates = currentImage’*baseImages;<br />
% Identifiera siffran som den som har störst 2-norm av<br />
% koordinaterna<br />
normCoords(number + 1) = norm(coordinates);<br />
if norm(coordinates) > maxNorm<br />
maxNorm = norm(coordinates);<br />
idNumber = number;<br />
end<br />
end