13
$\begingroup$

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?

$\endgroup$
  • 1
    $\begingroup$ Is this answer of any help? $\endgroup$ – C. E. Apr 24 '17 at 18:40
5
$\begingroup$
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

$\endgroup$
  • $\begingroup$ 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. $\endgroup$ – Alexey Popkov Apr 25 '17 at 16:51
5
$\begingroup$

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

$\endgroup$
  • $\begingroup$ Fun,since we want to restore the image,then we have no the information of img.Then we cannot use ImageDimensions@img? $\endgroup$ – yode Apr 25 '17 at 21:29
  • $\begingroup$ @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. $\endgroup$ – Alexey Popkov Apr 25 '17 at 22:16
  • $\begingroup$ @AlexeyPopkov Neat !! +1 $\endgroup$ – Ali Hashmi Apr 26 '17 at 0:24
4
$\begingroup$

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

$\endgroup$
  • $\begingroup$ Since we have img, you should simply use dim = ImageDimensions[img] for obtaining the dimensions of the original image. Then your solution won't be "durty" anymore. ;) $\endgroup$ – Alexey Popkov Apr 28 '17 at 12:54
  • $\begingroup$ @AlexeyPopkov Pretty a little,thanks. $\endgroup$ – yode Apr 28 '17 at 13:08
  • $\begingroup$ I've added a short description of your method, I hope you don't mind. :) $\endgroup$ – Alexey Popkov Apr 28 '17 at 18:55
  • $\begingroup$ @AlexeyPopkov Not at all,be my guest.But actually I havenot see the Ali's answer.If I have,I would not post it. :) $\endgroup$ – yode Apr 28 '17 at 21:11
3
$\begingroup$
{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

$\endgroup$
  • $\begingroup$ 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) $\endgroup$ – Ali Hashmi Apr 24 '17 at 20:05
  • $\begingroup$ @AlexeyPopkov please feel free to modify the answer. will be happy to see the modification ! $\endgroup$ – Ali Hashmi Apr 24 '17 at 20:19
  • $\begingroup$ @AlexeyPopkov at the end of both methods we can replace all background pixel values to the desired colour $\endgroup$ – Ali Hashmi Apr 24 '17 at 20:25
2
$\begingroup$

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

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.