Lényege: valamilyen szempont szerint homogén csoportok képzése a pixelekből. Amit már ismerünk: Küszöbölés, vágás, sávkijelölés hátránya: az azonos csoportba sorolt pixelek nem feltétlenül alkotnak összefüggő halmazt.
Tekintsük a képet egy domborzati térképként.
Képzeljük el, hogy a (környezettől erősen eltérő) lokális minimumhelyeknél feltört a víz. Az üregek folyamatosan töltődnek fel vízzel. Ahol a különböző forrásokból származó víz összefolyna, ott gátat képzünk (szegmensek határai).
Töltsd le a sejteket tartalmazó képet, és olvasd be színesben. https://arato.inf.unideb.hu/szeghalmy.szilvia/kepfeld/img/sejtek.bmp Állíts elő két maszkot, az egyiken a sötétlila, a másikon a sötétbarna sejtek legyenek sötétbarna HSV tartomány: (0, 10, 0) -tól (30, 255, 150) -ig sötétlila HSV tartomány : (80, 10, 0) -tól (170, 255, 150) -ig (javasolt a cvtconvert és az inrange használta) Hozz létre egy képet a markereknek (a "feltörő források") A kép típusa: CV_32S, mérete az eredeti képével azonos Kezdetben minden pixel fekete. A sötétbarna pixeleket állítsd be 128-as értékre. (javasolt: marker.setto( 128, barna_maszk) ) A sötétlila pixeleket állítsd be 255-ös értékre. (Más pozitív érték is lehet, de ezek jól láthatóan eltérnek, ha megjeleníted a képet 255-tel szorozva.) Hajtsd végre a watershed trf-et. watershed(színes_bemeneti_kep, marker_kep); A marker képet convertált CV_8UC1-re vagy rajzold át a 128-as, ill. 255-ös értékű pontokat egy új képre és jelenítsd meg. (javasolt az első megoldás: marker_kep.convertto( uj_kep, CV_8U, 1.0))
Töltsd le a következő képet és olvasd be színesben: https://arato.inf.unideb.hu/szeghalmy.szilvia/kepfeld/img/skate.jpg Állítsd elő a lokális minimumhelyeket tartalmazó maszkot Alakíts szürkeskálássá a képet. Készíts egy s s méretű, CV_8U típusú mátrixot, melynek minden pontja 1, kivéve a középpontja, mely 0. ( s értéke szabályozza a lokális mininum helyek közti távolságot, kb. 5, 7) Erodáld a képet a mátrixszal: erode( a_szurkeskalas_kep, a_cel_kep, a_matrix); lokalis_minimum = a_szurkeskalas_kep < a_cel_kep; //megj: homogén részre nem reagál Hozz létre egy képet a markereknek (a "feltörő források") A kép típusa: CV_32S, mérete az eredeti képével azonos Kezdetben minden pixel fekete. Az összes lokális minimum értéket állítsd egynél nagyobb, egymástól különböző értékre. (pl. bejárod a lokalis_minimum képet, és ha találsz egy előtérpontot, akkor növelsz egy nulláról induló számlálót. A növelt számláló értékét állítod be a markerkép adott pontjában értéknek. Hajtsd végre a watershed trf-et. watershed(színes_bemeneti_kep, marker_kep);
Hozz létre egy szegmens struktúrát / osztályt struct Segment{ vector<cv::point> pts; float feature; //lehet több is int idx; //egy szegmens indexe ; Készíts függvényt, mely összegyűjti a watershed trf. eredményképe alapján a szegmenseket. A numlabel a lokális minimumhelyek száma (watershed trf-nél meghatároztad) void collectsegments(cv::mat_<int> markers, vector<segment>& segments, int numlabel){ A segments vektor méretezd át numlabel-re. Járd be a markerképet m jelölje az aktuális pont markerkép értékét Ha m eleme a [0, numlabel-1] tartománynak akkor a segmens vektor m. eleméhez add hozzá az aktuális pontot //segments[m].push_back( Point(aktualis_pont_koordinatai) );
Készíts függvényt, mely beállítja az előbb begyűjtött szegmensek jellemzőit A numlabel a lokális minimumhelyek száma (watershed trf-nél meghatároztad, ismered) Ha rgb képet akarsz majd átadni, akkor az img-t alakítsd szürkeskálásra cvtconvert( img, gray, COLOR_BGR2GRAY ); void setsegmentsfeatures(const cv::mat img, vector<segment>& segments) { //járd be a szegmens vektort (sima for ciklus) az aktuális szegmes tulajdonságait állítsd be: idx értéke legyen a vektorban elfoglalt helye (ciklusváltozó) feature értéke legyen a szegmens pontjainak átlagos szürkeségi értéke a pts vektor pontjai és az img alapján meghatározhatod
Készíts függvényt, mely meghatározza a hasonló szegmenseket. (ez a fajta megoldás az egymástól távol álló szegmenseket is hasonlónak tudja tekinteni) Az eredmény egy hasonlósági mátrix. 1-es áll egy mezőben, ha az adott sornak megfelelő indexű szegmens hasonlít az adott oszlopban álló szegmensre. A tárolás a felső háromszögben történjen (vagy legyen szimmetrikus) void segmentsformerge(vector<segment>& segments, cv::mat& mergemat, int threshold) { Hozd létre csupa nullával feltöltve a hasonlósági mátrixot (mergemat) mérete a segmensek számával azonos Járd be a szegmenseket ( i = 0; ) Járd be az i-től nagyobb sorszámú szegmenseket ( j = i+1; ) Ha a két szegmens feature értékének eltérése a megadott küszöbérték (threshold) alatt van, akkor a mátrix (i, j). eleme legyen 1-es értékű.
Ha szeretnéd, hogy egymástól távoli szegmensek ne kapcsolódhassanak közvetlenül, akkor a marker képen távolítsd el a határoló vonalakat void dilatemarkers(cv::mat_<int>& markers){ Járd be a mátrixot Ha -1-et értéket találsz, írd felül egy nem -1 értékű szomszédjával. Hozz létre egy szomszédsági mátrixot (szimmetrikus / felső háromszög), ez alapján lehet majd törölni az előző mergemat-ból a nem kívánt elemeket. void neigboursegments(mat_<int> markers, cv::mat& neigbourmat, int numseg) { neigbourmat = Mat::zeros(cv::Size(numSeg, numseg), CV_8UC1); //járd be a marker képet (vigyázz, hogy majd a szomszéd is beférjen a tartományba) //A marker értékek a szegmensek indexével azonosak a korábbi feltöltés miatt jelölje s1 az (i, j) és s2 a (i, j+1) pont markerét Ha egyik sem -1, akkor jelöld a s1. és s2. szegmenst szomszédosnak * neigbourmat.at<uchar>(s1, s2) = neigbourmat.at<uchar>(s2, s1) = 1; Ugyanezeket a lépéseket tedd meg a (i, j) és (i+1, j) pontokra is. *(a dilatemarkers-nél választott szomszédoktól függően maradhat-1-es érték) akkor
Szegmens összeolvasztás, ha a pontosorzatokkal nem foglalkozunk void mergesegments(vector<segment>& segments, cv::mat_<uchar> mergemat) { Járd be a mergemat mátrix FELSŐ háromszög részét ( for( i = 0; ) for (j = i+1 ) ha a mergemat aktuális eleme nem nulla, akkor a j. szegmens indexét állítsd át az i. szegmens indexére. Szegmens összeolvasztás, ha a szegmensek pontjait is meg akarjuk kapni void mergesegments(vector<segment>& segments, cv::mat_<uchar> mergemat) { //ilyenkor visszafelé haladunk (hogy a már összevontat vonjuk később össze) for (int i = mergemat.rows-1; i >=0; --i) { for (int j = mergemat.cols - 1; j >= i + 1; --j) { ha a mergemat aktuális eleme nem nulla, akkor az i. szegmens pontjaihoz (pts) add hozzá a j. szegmens pontjait. //segments[i].insert( )) majd töröld a j. szegmensből a pontokat //segments[j].cleare();
Az alábbi függvény kirajzolja a szegmenseket void drawsegment(cv::inputoutputarray dest, const vector<segment>& segments) { vector<cv::vec3b> lookup( segments.size()); for (int i = 0; i < segments.size(); ++i) lookup[i] = cv::vec3b(rand() % 250 + 50, rand() % 255, rand() % 255); cv::mat& destmat = dest.getmatref(); for (auto s : segments) { if (s.pts.size() > 0) { cv::vec3b color = lookup[s.idx]; for (auto p : s.pts) destmat.at<cv::vec3b>(p) = color;
És végül a függvény, ami az egészet összefogja: void hiersegment(const Mat_<cv::Vec3b> img, Mat_<int> markers, Mat& dest, int numseg, float threshold = 20.0, bool mergeonlyneighbour = false) { //körvonalak eltávolítása a marker képről (mergeonlyneighbour igaz értékénél fontos) dilatemarkers(markers); vector<segment> segments; collectsegments( markers, segments, numseg); setsegmentsfeatures(img, segments); cv::mat mergemat; segmentsformerge(segments, mergemat, threshold); if (mergeonlyneighbours) { //csak ha megcsináltad cv::mat neighbourmat; neigboursegments(markers, neighbourmat, numseg); mergemat &= neighbourmat; mergesegments(segments, mergemat); dest.create(markers.size(), CV_8UC3); drawsegment(dest, segments);
Eredeti Watershed (beépített) (gauss 3x3, sigma=2) markerek a lokális maximumhelyek (5x5) Hasonló szegmensek összevonása összevonás küszöbértéke 15 jellemző: átlagos intenzitás Hasonló, 4-szomszédság szerint kapcsolódó szegmensek összevonása után