LUNDS TEKNISKA HÖGSKOLA 1(6) Institutionen för datavetenskap Tentamen, Programmeringsteknik för BME, F och N 2015 06 03, 14.00 19.00 Anvisningar: Preliminärt ger uppgifterna 7 + 11 + 16 + 11 = 45 poäng. För godkänt betyg krävs 22,5 poäng. Tillåtet hjälpmedel: Java-snabbreferens. Lösningsförslag kommer att finnas på kursens hemsida senast dagen efter tentamen. Resultatet läggs in i Ladok när rättningen är klar och anslås inte på anslagstavlan. Tentamensvillkor: Om du tenterar utan att vara tentamensberättigad annulleras din skrivning. För att undvika att någon skrivning annulleras av misstag kommer alla som, enligt institutionens noteringar, tenterat utan att vara tentamensberättigade att kontaktas via epost. Felaktigheter i institutionens noteringar kan därefter påtalas fram till nästa tentamenstillfälle då resterande skrivningar annulleras.
2(6) 1. En digital bild kan betraktas som en matris av pixlar. (Med pixel avses bildens minsta beståndsdel, en färgad punkt.) I en färgbild har varje pixel en färg; i en gråskalebild varierar pixlarna mellan vitt och svart med ett antal gråskalor däremellan. Här kommer vi att arbeta med sådana gråskalebilder. Varje pixel antar ett heltalsvärde i intervallet [0, 255], där 0 representerar helt svart och 255 helt vitt. Bilden kan därmed representeras som en matris av heltal. Du kommer att implementera klassen GrayscaleImage, som beskriver en gråskalebild. Klassen har inledningsvis följande specifikation: GrayscaleImage /** Skapar en gråskalebild med innehåll läst från filen filename. */ GrayscaleImage(String filename); /** Tar reda på bildens höjd (antalet pixelrader). */ int getheight(); /** Tar reda på bildens bredd (antalet pixlar på varje rad). */ int getwidth(); /** Tar reda på pixelvärdet på rad r och kolonn c. Om r eller c pekar utanför bilden returneras värdet på den närmsta pixeln. */ int get(int r, int c); Till din hjälp har du klassen ImageReader, som är färdigskriven och används för att läsa in en bild från fil. Den har följande specifikation: ImageReader /** Läser in en bildfil med givet filnamn, omvandlar den till gråskala och returnerar en matris av heltalsvärden i intervallet 0..255. Matrisens dimensioner överensstämmer med bildens. */ static int[][] readimage(string filename); Uppgift: Implementera klassen GrayscaleImage. Ledning för get: Parametrarna r och/eller c kan ligga utanför intervallen [0, bildhöjd 1] respektive [0, bildbredd 1]. Ersätt då negativa koordinater med 0 och alltför stora med bildhöjd 1 respektive bildbredd 1. Dessa justerade koordinater ger den närmsta pixeln.
3(6) 2. I denna uppgift ska du införa metoder för att behandla en digital bild. Bilden är inledningsvis brusig, och även om vi kan förstå dess innehåll med blotta ögat krävs en del behandling för att datorbaserad analys ska bli möjlig. I figur 1 nedan visas bildbehandlingen i två steg. Bilderna föreställer ett antal röda blodkroppar från en groda. Den första, originalbilden (a), är brusig och kan inte utan vidare analyseras. I den andra bilden (b) har bruset reducerats. I den tredje bilden (c) har föremålen skilts ut från bakgrunden. Fig. 1(a): originalbild Fig. 1(b): efter brusreducering Fig. 1(c): trösklad Brusreduceringen (a b ovan) utförs i metoden smooth. Tröskling, där föremålet skiljs från bakgrunden (b c ovan) utförs i metoden threshold. Metoderna har följande specifikationer: /** Brusreducerar bilden genom medelvärdesbildning. 1. En ny matris skapas, där varje pixels värde är medelvärdet av motsvarande 3x3 pixlar i originalbilden. 2. Den nya matrisen ersätter därefter originalet. */ void smooth(); /** Går igenom bilden pixel för pixel. Om pixelns värde är >= t sätts det till 255, annars till 0. */ void threshold(int t); I figur 2 nedan illustreras medelvärdesbildningen i smooth. Varje pixel i det brusreducerade resultatet, exempelvis (53,43), beräknas som medelvärdet av 3 3 pixlar i originalbilden. Pixlarna är här numrerade enligt (rad,kolonn). Fig. 2: Medelvärdesbildning. (255 + 255 + 29 + 107 + 255 + 29 + 107 + 255 + 255)/9 = 171 (trunkerat pga. heltalsdivision). Uppgift: Implementera metoderna smooth och threshold i klassen GrayscaleImage. Ledning för smooth: Inför gärna en privat hjälpmetod för medelvärdesberäkningen kring en given pixel. I medelvärdesberäkningen måste man hantera pixlarna i bildens kanter på något sätt en del av de 3 3 pixlar som behövs saknas där. I denna beräkning ska du ersätta varje sådan saknad granne med den närmsta pixeln från originalbilden. Metoden get (från uppgift 1) ska användas för att lösa detta.
4(6) 3. Inför den fortsatta bildbehandlingen ska vi lösa ett delproblem. Ibland behöver man gruppera heltal i ekvivalensklasser. Ordet klass syftar inte här på Java-klass, utan på att vissa heltal anses höra samman på något vis. Vi kan exempelvis ange att talen 1 och 3 hör ihop, liksom talen 7 och 8, och även talen 5 och 9. Då får vi följande ekvivalensklasser: 1 3 7 8 5 9 Om vi nu anger att talen 2 och 7 hör ihop, utökas en av ekvivalensklasserna: 1 3 2 7 8 5 9 Om vi vidare anger att talen 1 och 5 hör ihop, slås två ekvivalensklasser samman: 1 3 5 9 2 7 8 Ett heltal kan tillhöra högst en ekvivalensklass. Java-klassen EquivalenceClasses beskriver en lista av sådana ekvivalensklasser. Den har följande specifikation: EquivalenceClasses /** Skapar en ny lista över ekvivalensklasser. */ EquivalenceClasses(); /** Anger att a och b ska tillhöra samma ekvivalensklass. - Om a och b båda redan ingår i olika ekvivalensklasser, så ska de två ekvivalensklasserna ersättas av en sammanslagen. - Om ett av talen ingår i en ekvivalensklass, men inte det andra, läggs ett tal till i en ekvivalensklass. - Om varken a eller b ingår i någon ekvivalensklass skapas en ny. */ void join(int a, int b); /** Tar reda på det minsta heltal i den ekvivalensklass där n ingår. Om n inte ingår i någon ekvivalensklass returneras n självt. */ int least(int n); Exempel: Följande satser skriver ut talen 3 och 5: EquivalenceClasses table = new EquivalenceClasses(); table.join(3, 7); table.join(7, 9); table.join(1, 4); System.out.println(table.least(9)); // ingår i ekvivalensklassen {3,7,9} System.out.println(table.least(5)); // ingår inte i någon ekvivalensklass Uppgift: Implementera klassen EquivalenceClasses. Till din hjälp har du klassen IntSet. Denna klass, som du inte ska implementera, beskriver en mängd av heltal. Specifikationen för IntSet följer på nästa sida. Ledning för EquivalenceClasses: Använd en lista av IntSet-objekt för att hålla reda på ekvivalensklasserna. Inför gärna en privat hjälpmetod, t. ex. private IntSet findset(int n) för att söka upp den mängd som innehåller ett givet tal. Om du behöver ta bort ett element ur en ArrayList kan du använda metoden remove (se snabbreferensen).
5(6) IntSet /** Skapar en tom mängd av heltal. */ IntSet(); /** Lägger till n till denna mängd. Om n redan ingår händer inget. */ void add(int n); /** Lägger till alla heltal från mängden s till denna mängd. */ void addall(intset s); /** Tar reda på om denna mängd innehåller talet n. I så fall returneras true, annars false. */ boolean contains(int n); /** Tar reda på det minsta talet i denna mängd, eller 0 om mängden är tom. */ int least();
6(6) 4. Vi återvänder nu till gråskalebilderna. I en digital bild vill vi numrera de föremål som visas på bilden. Föremålen förknippas alltså med varsitt heltal. Det är inte nödvändigt att heltalen är just 1, 2, 3,..., bara att de är unika. Denna numrering kallas märkning, och innebär att pixlar som tillhör samma föremål ges samma värde. Märkningen utgår från en trösklad bild. I figur 3(a) nedan har blodkropparna från uppgift 2, figur 1(c), märkts. Vidare analyser är nu möjliga. Man kan exempelvis räkna blodkropparna och jämföra deras storlekar. Fig. 3(a): märkta blodkroppar Fig. 3(b): omärkt Fig. 3(c): specialfall Märkningen bygger på följande idé. Vi går igenom bildens pixlar, rad för rad, och varje rad från vänster till höger. För varje pixel som tillhör ett föremål undersöker vi två andra pixlar: den ovanför och den till vänster. Om någon av dessa två (ovanför/vänster) är märkt, så låter vi den färga av sig till den aktuella pixeln. Annars anses pixeln tillhöra ett nytt föremål, och märks med ett eget värde. Ett specialfall inträffar då dessa två (ovanför/vänster) båda är märkta, men med olika värden. Ett exempel visas i figur 3(c) ovan, då pixeln med frågetecknet ska märkas. Man väljer då ett av de två värdena till den aktuella pixeln, och gör en notering om att de två värdena (1 och 2 i figuren) är ekvivalenta. De tillhör ju båda nu samma föremål. Efteråt gås bilden igenom en andra gång. Då får alla pixlar i varje föremål samma värde utifrån de ekvivalenser som upptäckts. Föremålet i figur 3(b) (c) kan då märkas med värdet 1 för alla pixlar. Följande algoritm följer den idé som beskrivits ovan: 1. Låt n vara ett heltal, inledningsvis 1. 2. Gå igenom bildens alla pixlar, rad för rad, och på varje rad från vänster till höger. En pixel tillhör ett föremål om dess värde är < 255. Om den aktuella pixeln tillhör ett föremål beräknas dess nya värde a som följer: Låt p 1 vara pixelvärdet närmast till vänster om pixeln. Låt p 2 vara pixelvärdet närmast ovanför pixeln. Om p 1 tillhör ett föremål, men inte p 2, låt a = p 1. Om p 2 tillhör ett föremål, men inte p 1, låt a = p 2. Om både p 1 och p 2 tillhör föremål, låt a = p 1. Om p 1 och p 2 är olika (och båda tillhör föremål), notera att p 1 och p 2 är ekvivalenta. Om varken p 1 eller p 2 tillhör föremål, låt a = n, och öka n med 1. 3. Gå därefter igenom bildens alla pixlar en andra gång. Varje pixel, som tillhör ett föremål, får sitt värde a ersatt med det minsta pixelvärde som är ekvivalent med a. Märkningen görs i metoden label i klassen GrayscaleImage. Metoden har följande specifikation: /** Märker vart och ett av bildens föremål med en egen grå nyans. */ void label(); Uppgift: Implementera metoden label i klassen GrayscaleImage. Algoritmen ovan ska användas. Ledning: Du ska använda klassen EquivalenceClasses (från uppgift 3) för att hålla reda på vilka pixelvärden som är ekvivalenta. Du kan utgå från att föremålen är långt färre än 255. Du kan utgå från att föremålen omges av en ram av bakgrundspixlar (255). Du kan utgå från att bilden är trösklad, det vill säga bara innehåller pixelvärdena 0 och 255.