14

Suppose I have an image containing several morphological components and I wish to extract them as individual images (with sizes equal to their bounding boxes; these individual images shouldn't contain parts of neighboring components), then apply some image-processing functions to each of them individually, and finally combine them backward keeping the position of the each as it was in the original image. Note that bounding boxes of the components can intersect with each other.

Here is an example:

img = Import["https://i.stack.imgur.com/QwfYD.png"]

image

m = MorphologicalComponents[img];
cm = ComponentMeasurements[{m, ColorNegate@img}, {"MaskedImage", "BoundingBox"}];
newComps = ImageAdjust /@ DistanceTransform /@ ColorNegate /@ cm[[;; , 2, 1]]

output

The question:
How to assemble the transformed components newComps into the complete image?

Alexey Popkov
  • 61,809
  • 7
  • 149
  • 368

5 Answers5

6

Here is a SparceArray version of Ali's second method which is expected to be more memory-efficient (at least for images of type "Real"):

{iW, iH} = ImageDimensions@img; 

Image[Total[
  Table[SparseArray[
    Band[1 + Round@{iH - #[[2, 2]], #[[1, 1]]} &@cm[[i, 2, 2]]] -> 
     ImageData[newComps[[i]]], {iH, iW}], {i, Length[cm]}]]]

image

Procedural summation is even more memory-efficient:

Module[{sum = 0}, 
 Do[sum += SparseArray[
    Band[1 + Round@{iH - cm[[i, 2, 2, 2, 2]], cm[[i, 2, 2, 1, 1]]}] -> 
     ImageData[newComps[[i]]], {iH, iW}], {i, Length[cm]}]; Image[sum]]

image

Image[Fold[Plus[#1, 
    SparseArray[
     Band[Round@{-cm[[#2, 2, 2, 2, 2]], 1 + cm[[#2, 2, 2, 1, 1]]}] -> 
      ImageData[newComps[[#2]], Automatic], {iH, iW}]] &, 0, Range[Length@cm]]]

image


Here is how this method can be applied to a three-channel RGB image (the purpose here is to ImageAdjust the components of the image independently from each other):

img = Import["https://i.stack.imgur.com/U7zdU.png"]

image

m = MorphologicalComponents[img, .3];
cm = ComponentMeasurements[{m, img}, {"MaskedImage", "BoundingBox"}];
newComps = ImageAdjust@ImageMultiply[RemoveAlphaChannel@#, AlphaChannel[#]] & /@ 
  cm[[;; , 2, 1]]

output

{iW, iH} = ImageDimensions@img;

Image[Total[
  Table[SparseArray[
    Band[Round@{1 + iH - #[[2, 2]], 1 + #[[1, 1]], 1} &@cm[[i, 2, 2]]] -> 
     ImageData[newComps[[i]]], {iH, iW, 3}], {i, Length[cm]}]]]

image


Combining everything into one function:

assembleComponents[newComps_, boundingBoxes_, {iW_, iH_}] := 
 Module[{sum = 0, iCh = ImageChannels[newComps[[1]]]},
  If[iCh == 1, 
   Do[sum += SparseArray[
      Band[Round@{iH - boundingBoxes[[i, 2, 2]] + 1, boundingBoxes[[i, 1, 1]] + 1}] -> 
       ImageData[newComps[[i]], Automatic], {iH, iW}], {i, Length[boundingBoxes]}],
   Do[sum += 
     SparseArray[
      Band[Round@{iH - boundingBoxes[[i, 2, 2]] + 1, boundingBoxes[[i, 1, 1]] + 1, 1}] -> 
       ImageData[newComps[[i]], Automatic], {iH, iW, iCh}], {i, Length[boundingBoxes]}]]; 
  Image[sum]]

Usage:

assembleComponents[newComps, cm[[;; , 2, 2]], ImageDimensions[img]]

image

Alexey Popkov
  • 61,809
  • 7
  • 149
  • 368
  • Fun,since we want to restore the image,then we have no the information of img.Then we cannot use ImageDimensions@img? – yode Apr 25 '17 at 21:29
  • @yode No, you misunderstood the question. We don't need to restore the original image, we have it. We need to assemble processed components into the complete new image in a way that each of them is placed exactly where it was in the original image. – Alexey Popkov Apr 25 '17 at 22:16
  • @AlexeyPopkov Neat !! +1 – Ali Hashmi Apr 26 '17 at 00:24
5
Module[{seg, img = img, cm, newComps, func},

seg = MorphologicalComponents[img];
cm = ComponentMeasurements[{seg, ColorNegate@img}, {"MaskedImage","BoundingBox"}];
newComps = ImageAdjust /@ DistanceTransform /@ ColorNegate /@ cm[[;; , 2, 1]];

func[matrix_, {ind_, imgs_}] := 
Block[{mat = matrix, cellindpos, smallImgData, vals},
cellindpos = Position[seg, ind];
smallImgData = ImageData@imgs;
vals = Extract[#, Position[#, x_ /; x != 0]] &@smallImgData ;
ReplacePart[mat, Thread[cellindpos -> vals]]
];

Fold[func, ConstantArray[0, Reverse@ImageDimensions@img],
Thread[{Range@Length@newComps, newComps}]]] // Image

enter image description here

Ali Hashmi
  • 8,950
  • 4
  • 22
  • 42
  • This method assumes that the indices of each of the components do not change during processing of the subimages. This assumption is very restrictive and isn't correct in general. Your second method is much better since it doesn't imply such an assumption and at the same time is much simpler and more efficient. – Alexey Popkov Apr 25 '17 at 16:51
4

An ArrayPad version of Ali's second method:

data = KeyValueMap[
  Round[Transpose[#2]] -> ImageData[#1] &, <|Thread[newComps -> cm[[All, -1, -1]]]|>];
dim = ImageDimensions[img];
Image[Plus @@ (ArrayPad[Values[#], {{dim[[2]] - Keys[#1][[2, 2]], 
   Keys[#1][[2, 1]]-1}, {Keys[#1][[1, 1]]-1,dim[[1]] - Keys[#1][[1, 2]]}}] & /@ data)]

Mathematica graphics

Alexey Popkov
  • 61,809
  • 7
  • 149
  • 368
yode
  • 26,686
  • 4
  • 62
  • 167
3
{comps, boxes} = Module[{seg, img = img, cm, newComps, bounds},
seg = MorphologicalComponents[img];
cm = ComponentMeasurements[{seg, ColorNegate@img}, {"MaskedImage","BoundingBox"}];
bounds = cm[[All, 2, 2]];
newComps = ImageAdjust /@ DistanceTransform /@ ColorNegate /@ cm[[;; , 2, 1]];
{newComps, bounds}];

{iW, iH} = ImageDimensions@img

ImageAdd[MapThread[ImagePad[#1, {{#2[[1, 1]], iW - #2[[2, 1]]}, {#2[[1, 2]], 
  iH - #2[[2, 2]]}}] &, {comps, boxes}]]

enter image description here

Ali Hashmi
  • 8,950
  • 4
  • 22
  • 42
  • This method uses ImagePad and since the approach is different hence posting it as a separate answer. Also the image obtained from this and the previous answer are both same (tested using SameQ) – Ali Hashmi Apr 24 '17 at 20:05
  • @AlexeyPopkov please feel free to modify the answer. will be happy to see the modification ! – Ali Hashmi Apr 24 '17 at 20:19
  • @AlexeyPopkov at the end of both methods we can replace all background pixel values to the desired colour – Ali Hashmi Apr 24 '17 at 20:25
2

If instead of "BoundingBox" we request "Mask" the solution becomes more elegant and idiomatic:

cm = ComponentMeasurements[{m, ColorNegate@img}, {"MaskedImage", "Mask"}, 
   "PropertyAssociation"];

newComps = ImageAdjust /@ DistanceTransform /@ ColorNegate /@ cm["MaskedImage"];

pos = Min /@ Transpose[#["NonzeroPositions"]] & /@ cm["Mask"];

Image[Total[
  Table[SparseArray[Band[pos[[i]]] -> ImageData[newComps[[i]]], 
    Reverse[ImageDimensions@img]], {i, Length[newComps]}]]]

image

Alexey Popkov
  • 61,809
  • 7
  • 149
  • 368