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

5 Answers 5

6
$\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$
3
  • $\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, 2017 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$ Apr 25, 2017 at 22:16
  • $\begingroup$ @AlexeyPopkov Neat !! +1 $\endgroup$
    – Ali Hashmi
    Apr 26, 2017 at 0:24
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$
1
  • $\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$ Apr 25, 2017 at 16:51
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$
4
  • $\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$ Apr 28, 2017 at 12:54
  • $\begingroup$ @AlexeyPopkov Pretty a little,thanks. $\endgroup$
    – yode
    Apr 28, 2017 at 13:08
  • $\begingroup$ I've added a short description of your method, I hope you don't mind. :) $\endgroup$ Apr 28, 2017 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, 2017 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$
3
  • $\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, 2017 at 20:05
  • $\begingroup$ @AlexeyPopkov please feel free to modify the answer. will be happy to see the modification ! $\endgroup$
    – Ali Hashmi
    Apr 24, 2017 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, 2017 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 and acknowledge you have read our privacy policy.

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