outline 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. This is the draft pseudocode for the region growing segmentation algorithm. More information, references, requirements, etc are at the wiki:
  2. http://grass.osgeo.org/wiki/GRASS_GSoC_2012_Image_Segmentation
  3. For anyone interested in background discussion, Rev 51907 includes original comments and discussion between EM, MM, and ML. All comments were combined (and new threads started!) after that revision.
  4. TODO: Memory managment, allow user to imput how much RAM can be used. Compare i.watershed and i.cost for two current options, initial recommendations are to follow i.watershed.
  5. TODO: Are any parts here potentially useful as library functions? Are any of these tasks already done by existing library functions?
  6. General notes:
  7. Avoid use of global variables (makes multithreading easier).
  8. files:
  9. iseg.h - declare structures and functions
  10. Structures:
  11. files: input and output data (segmentation files and/or RAM storage)
  12. functions: parameters and function pointers
  13. /****************************************************/
  14. /******** Parse the input parameters ****************/
  15. /****************************************************/
  16. Using just imagery group right now. Could add an option to allow just one raster as an input (user friendly). Currently no plan for subgroup, it is not often used.
  17. seeds: All pixels or user defined points (optional, default = all pixels) user defined points will initially be defined with a raster map, could add option to allow vector map later (user friendly).
  18. constraint (vector linear/polygon) (optional)
  19. segmentation algorithm (only region growing accepted to start)
  20. pixel neighbors: for a particular pixel, use 4 neighbors or 8 neigbors? (flag, not set = 4 neighbors)
  21. Later: memory usage (r.watershed uses -m flag + memory parameter)
  22. /* Algorithm parameters */
  23. similarity threshold
  24. how quickly the threshold should be reduced
  25. Minimum number of pixels per segment (optional, default 1)
  26. Later:
  27. weights for shape and compactness
  28. No plans for implementation:
  29. layer weights, color spaces, color transformations: these should be done by the user prior to running i.segment.
  30. /* output parameters */
  31. need name for new raster map
  32. Later:
  33. maybe add some glue to allow the user to automatically launch r.to.vect and then the stats module.
  34. /************************************/
  35. /******** Input Validation **********/
  36. /************************************/
  37. Exit at first failure:
  38. confirm input algorithm parameters are in correct range
  39. Todo: confirm if Rast_get_row() and nulls are handled in a way later processing steps can deal with. (nulls should be skipped)
  40. Grass library functions already handle basic checks (overwrite, conversion from source data resolution to region resolution, etc)
  41. End of validation checks
  42. /************************************/
  43. /******** Open maps **********/
  44. /************************************/
  45. Open the input and output maps.
  46. Todo: Currently using the segmentation library. need to implement the -m option to allow possibility to store in RAM.
  47. input map structure:
  48. - each cell is an array, length to match number of maps in the input group
  49. - TODO: how to store optional vector boundaries
  50. output map structure:
  51. - also an array:
  52. - segment assignment
  53. - candidate flag: has it been check during this iteration loop
  54. - assigned flag: has it been assigned to a segment (only if number of seeds < number of pixels)
  55. /*******************************************/
  56. /************* preprocessing ? *************/
  57. /*******************************************/
  58. Any preprocessing?
  59. If complete polygons are given as boundaries:
  60. 1. run segmentation for each polygon, mask rest of map
  61. 2. run segmentation with polygons as mask (to segment anything not included in a polygon)
  62. I think this will apply to vector lines only, initially it was framed as being for polygons or lines.
  63. /*ML: vector to raster conversion is probably necesary. Pixels crosses by a line (polygon boundary or not) have to become part of a segment boundary.*/
  64. /*EM: hmm, OK, something else for discussion: These pixels that are on a vector line, should they eventually be included in one of the adjacent segments? Is "segment boundary" just the edge pixels of the segment, or are the not included in any segment?*/
  65. /*ML: Here is where a difference comes into play between lines that are boundary polgons and lines as linear features. In my eyes pixels that are on boundarylines of polygons should be part of the segments that are internal to that boundary. Linear features would have to be treated differently. During discussions with colleagues we did have some difficulties finding actual use cases for linear features. Maybe we can start with only polygon features and if the use case of a linear features comes up try to integrate that then ?*/
  66. /*EM: But for polygons covering the entire map, there is a segment on either side of the polygon line. If the line crosses the pixel, what should be done... It looks like this will not be a problem for multi-scalar segmentation, the polygons generated in a high level segmentation will be exactly between pixels. This will only be an issue for polygons generated elsewhere, smoothed, at different resolution, etc.*/
  67. /* MM: You can not know where the polygons are coming from, therefore you have consider all cases or, better, come up with a general solution. You will need to clone (substantial) parts of the t.to.vect module if you want to rasterize polygons/boundaries. If you do not rasterize, you will need to check for a boundary/line whenever you evaluate a neighbor. This could be sped up a bit by selecting all boundaries/lines crossing the current 3x3 neighborhood. The spatial selection of vector features is fast, but doing that for every cell/3x3 neighborhood can substantially slow down the module. You will also need to check if the boundary is actually part of an area (not an invalid boundary). Then you will need to check if the focus cell is inside the area, if not, if the neighbor is inside the area. Even though some spatial information gets lost by rasterization, I tend to recommend rasterization. In any case, taking into account boundaries/lines can easily become the bulk of the code, the most complex part of the code, and the most time-consuming component of the module. */
  68. /* EM: left the above discussion... unresolved. One thought: instead of storing the output as a raster, maybe it should be first converted to a map, edges representing the neighbor relationship. After we have a map, we could use the vector map to delete edges crossing the borders. This is done once, afterwards we never calculate neighbors, only check for edges. It seems this will be a very large memory structure to start with, but as the segmentation continues it will get smaller.
  69. /*******************************************/
  70. /************ Processing ********************/
  71. /*******************************************/
  72. notes:
  73. If seeds < number of pixels, candidate pixels must meet the additional requirement of not yet being assigned to a segment.
  74. If we have polygon constraints. Outer for loop to process the image one polygon at a time. (Need to check if all pixels are included in a polygon, otherwise process all those pixels last.)
  75. /*
  76. * Roughly based on SPRING
  77. * Bins, et al: Satellite Imagery Segmentation: A Region Growing Approach, 1996
  78. * http://marte.dpi.inpe.br/col/sid.inpe.br/deise/1999/02.05.09.30/doc/T205.pdf
  79. */
  80. /* Similarity threshold T(t)... as t increases, threshold for similarity is lower. SPRING used: T(t) = T(0)alphat, where T(0) > 0, t =0,1,2... and alpha <1 */
  81. /* MM: T(0) > ) ??? what exactly is t */
  82. /* EM: corrected formula, should be >0. t = time. It feels like the opposite of simulated annealing. At earlier iterations in the process, it is difficult to make merges. As time increases, it is easier. To start with, I'm just going to implement a constant threshold, the lowering concept can be addeded later. */
  83. For t (until no merges are made?)
  84. Set candidate flag to true/1 for all pixels
  85. For each pixel that candidate flag is true:
  86. function: find segment neighbors (Now we have list of pixels in current focus segment (Ri) and a list of neighbors)
  87. Calculate similarity between Ri and neighbors
  88. If it exists, Rk is both most similar, and also similarity is < T.
  89. function: find segment neigbors of Rk
  90. Calculate similarity between Rk and its neighbors
  91. IF (Ri is Rk's best neighbor) (so they agree, both are best match for each other)
  92. merge Ri and Rk: (probably as function?)
  93. update segment values for all pixels in Ri+Rk (mean)
  94. set candidate flag to false for all pixels in Ri+Rk
  95. select next Ri
  96. /* MM: I guess it will be important how to select the next candidate
  97. * (see above, FIFO or some kind of sorting) */
  98. /* EM: I don't think the order matters: since the algorithm only accepts mutually best neighbors. */
  99. Else
  100. set candidate flag to false for all pixels in Ri
  101. Use Rk as next Ri /* note, this is the eCognition technique. Seems this is a bit faster, we already have segment membership pixels
  102. loop
  103. Were any merges made for this time step?
  104. next t
  105. Force a merge of regions that are below minimum size threshold (just merge with most similar neighbor, no comparison with similarity threshold)
  106. /*****************************************/
  107. /******Function: Find Segment Neighbors **/
  108. /*****************************************/
  109. 1 1 2 3 4
  110. 1 9 9 9 5
  111. 9 9 9 6 5
  112. 7 7 7 7 9
  113. If the current segment being checked is 9
  114. Desired results:
  115. If no seeds: (can merge unassigned pixels and other segments)
  116. For diagonals:
  117. 1, 2, 3, 4, 5, 6, 7
  118. else
  119. 1, 2, 3, 4, 5, 7
  120. else (starting from seeds, so only want single unassigned pixels as neighbors, no merges with other segments allowed)
  121. for diagonals:
  122. 2, 3, 4, 6
  123. else
  124. 2, 3, 6
  125. Method 1: (using "rasters")
  126. Input could be single pixel or list of pixels.
  127. /* MM: what exactly is input? seeds? */
  128. /* EM: input here is the current focus pixel. If it is a fresh Row/Column pixel, it will be a single pixel.
  129. * If we just had Ri that was not mutually best neighbors with Rk, we'll use the member list of Rk as the new Ri.
  130. * So in that case it will be the list of pixels we already know are part of the new Ri.
  131. Put input in "to be checked" stack
  132. Put input in "current segment" list
  133. put input in "don't check" list
  134. /* MM: you could also determine the status of each pixel on-the-fly when visiting this neighbor */
  135. /* EM: I think that could work for the "don't check" list. But they will need to be initialized to zero each time...will that take too long?
  136. * It seems we would have to initialize the entire map for each function call? Or keep track of a window size for row/column that was processed?
  137. * /
  138. empty "neighbor" list
  139. While "to be checked" stack isn't empty:
  140. pop
  141. find pixel neighbors
  142. with pixel neighbors
  143. if in "don't check" list
  144. do nothing
  145. else
  146. put in "don't check" list
  147. if candidate pixel
  148. if segment ID = current segment ID
  149. add to "current segment" list
  150. add to "to be checked" stack
  151. else
  152. add to "neighbor" list
  153. next neighbor
  154. loop...
  155. return: neighbor list, pixels in segment list
  156. /* MM: what is the purpose of the neighbor list and the segment list? */
  157. /* EM: neighbor list: We will calculate the similarity between Ri and each neighbor.
  158. * All need to be checked for the smallest similarity value (most similar), so order doesn't matter.
  159. * If an Rk is selected, we'll run this routine on it, so will also have a segment membership list for Rk.
  160. * If a merge is made, we have a list of pixels to be updated, members of Ri and Rk. (again order doesn't matter.)
  161. * It seemed a list would be faster then querying the entire map for what pixels have a specific segment value, since that
  162. * query has to look at the entire map.
  163. * And it seems with the raster based storage, there was no easy way to store this for the whole time, it would be rechecked each iteration.
  164. * ... see below with the map for further comments */
  165. neighbor list will be a listing of pixels that are neighbors? Include segment numbers? Only include unique segments?
  166. Maybe the most complete return would be a structure array, structure to include the segment ID and a list of points in it? But the list of points would NOT be inclusive - just the points bordering the current segment...
  167. Method 2: (if build a map data structure at start of program)
  168. Using current pixel's segment ID's edges, return neighbors
  169. /* EM: So if we want to remember what pixels are part of each segment in a stable (instead of temporary) method, I think the
  170. * map data structure makes more sense. Each element in the map would have: segment ID, processing flags, current values (mean of input data),
  171. * current number of pixels, and a list (linked list, sorted array, etc) of pixel members (row,column). Edges would represent neighbor relations.
  172. *
  173. * My feeling is that this will be very slow to set up, and need more memory for the first iterations (compared to the raster version)
  174. * But after the number of segments is reduced and the membership in each segment is higher, processing time will be faster.
  175. *
  176. * My advisor recommends to implement both methods - to check the speed, and also as a code validation / logic validation / bug testing:
  177. * the segmented maps should be the same for both.
  178. */
  179. Existing GRASS functions don't seem to have this "neighbor of an area" concept. Am I missing something, should something be adapted, should I design this as a library function, or just write it as a function for this program?
  180. r.neighbors:
  181. is looking at the neighborhood that is a specified distance from one pixel
  182. r.watershed
  183. looks at the 4 or 8 adjacent neighbors. (can use this as basis for Find Pixel Neighbors function)
  184. r.buffer
  185. My current understanding: needs to know the min/max row and column that the feature is found in, it then scans that entire square, if the pixel is part of the feature, then checks distances around it. Seems looking for min/max for every segment number will be time consuming. Would need data structure to accomodate remembering what pixels are in each segment to use this.
  186. /*****************************************/
  187. /******Function: Find pixel Neighbors ****/
  188. /*****************************************/
  189. will use function pointer based on input, to select 4 or 8 neighbors
  190. /*****************************************/
  191. /******Function: calculate simularity ****/
  192. /*****************************************/
  193. Initially only Euclidean Distance
  194. NOTE: this is not a distance function of the coordinates at all!
  195. sqrt of the sum of squared differences between each of the input raster maps
  196. /****************************************/
  197. /************ Output ********************/
  198. /****************************************/
  199. renumber segments to have a sequential (1,2,3...) numbering?
  200. output raster convert segmentation file or RAM data structure to grass raster using Rast_put_row()
  201. G_message: total number of segments made.
  202. /*******************************/
  203. /********** tidy up ************/
  204. /*******************************/
  205. free memory, delete temp files
  206. exit - success!
  207. ###############################
  208. ###############################
  209. Statistics for the segmentation calculated in a seperate module
  210. Segmentation output = raster
  211. statistics module input and output = vector. The user will have to run r.to.vect between the two.
  212. Name?
  213. i.seg.stats
  214. i.segment.stats
  215. r.stats.seg
  216. -e Maybe an option for basic vs. extended output statistics
  217. calculate statistics to be saved in data table for the vector map
  218. one vector map of segments per hierarchy level with a series of attributes (I think this request would be handled by running the module for each hierarchy level? Or do we need to have an attribute for hierarchy level stored with a single vector map?)
  219. spectral attributes:
  220. per spectral band: mean, min, max, skewness
  221. combination of bands: brightness, indices (i.e. results of multi-band calculations)
  222. textural attributes: stdev (per-band and/or multi-band), mean difference to neighbor, Haralick texture features cf r.texture
  223. geometric/morphological attributes: area, perimeter, length/width measures, see also r.li
  224. /* MM: perimeter is resolution-dependent, see also the famous Florida coastline problem */
  225. context attributes: mean difference to all other regions in the same upper hierarchical level, relative localisation within upper hierarchical level, absolute localisation, number of objects in lower level
  226. depending on segmentation algorithm: raster map indicating for each pixel the probability of belonging to the segment it was put into, i.e. some measure of reliability of results (For region growing - should this be the similarity measure when it was merged? Or similarity measure of the pixel compared to the average?)
  227. /*ML: Not sure, but I would think that similarity between pixel and average of region it belongs to might be a good choice. Am not a specialist in statistics, but maybe it is possible to translate this into some form of probability of really "belonging" to that region (cf i.maxlik)*/
  228. /* MM: I guess here it is important to not confuse classification with segmentation */