4
$\begingroup$

There is a picture:

enter image description here

When I try to use MorphologicalTransform to detect the end points

pic = Thinning@Binarize@Import["https://i.stack.imgur.com/K8dm3.png"];
MorphologicalTransform[pic, "EndPoints"]

enter image description here

It will detect five points.But four point is expected.So I wanna find it out like this,but I get nothing

ImageFilter[If[#[[2, 2]] == 1 && Total[#, 2] == 2, 1, 0] &, pic, 1]

enter image description here

Confuse me.But as my comprehension to ImageFilter, I think my code is right.If I wanna use ImageFilter to implement it,how to improve my code?

$\endgroup$

4 Answers 4

3
$\begingroup$

UPDATE

In this particular case it is quite sufficient just to apply Pruning with 1 as the second argument:

i = Import["https://i.stack.imgur.com/K8dm3.png"];
ep = MorphologicalTransform[Pruning[Thinning@Binarize@i, 1], "EndPoints"];
PixelValuePositions[%, White]
{{271, 546}, {190, 471}, {694, 382}, {899, 366}}

The purpose of Pruning is to remove just one pixel which causes additional end point caught by "EndPoints":

PixelValuePositions[
 ImageDifference[Thinning@Binarize@i, Pruning[Thinning@Binarize@i, 1]], White]
{{357, 341}}

Original answer

One approach is to apply Pruning and then take "SkeletonEndPoints":

i = Import["https://i.stack.imgur.com/K8dm3.png"];
ep1 = MorphologicalTransform[Pruning[Thinning@Binarize@i, 150], "SkeletonEndPoints"]

image

PixelValuePositions[%, White]

{{271, 546}, {190, 471}, {694, 382}, {899, 366}}

In this particular case we can make Pruning much more efficient if we apply FillingTransform before Thinning:

ep2 = MorphologicalTransform[
 Pruning[Thinning@Binarize@FillingTransform@i, 4], "SkeletonEndPoints"];

ep1 == ep2
True 

Now we can ensure that we actually have found end points of the thinned image:

ppos = ImageValuePositions[ep2, White]
{{270.5, 545.5}, {189.5, 470.5}, {693.5, 381.5}, {898.5, 365.5}}
pic = Thinning@Binarize@i;
Show[ImageTrim[pic, {#}, 1], GridLines -> {Range[10], Range[10]}, ImageSize -> 100, 
   Method -> {"GridLinesInFront" -> True}] & /@ ppos

image list

We can check what these pixels correspond to in the original image:

Show[ImageTrim[ReplaceImageValue[i, # -> Red], {#}, 5], 
   GridLines -> {Range[10], Range[10]}, ImageSize -> 100, 
   Method -> {"GridLinesInFront" -> True}] & /@ ppos

image list

$\endgroup$
7
  • $\begingroup$ I'm really don't know what the Pruning do.But it will lead to a right result. Can you give a explaination? $\endgroup$
    – yode
    Mar 21, 2016 at 15:45
  • $\begingroup$ @yode Citing the Documentation, "Pruning[image,n] removes branches that are at most n pixels long." In the other words, it computes lengths of branches of the skeleton an then "eats up" all branches with length no longer than n pixels. There is no explicit definition for the "length" but the principle seems to be clear. $\endgroup$ Mar 21, 2016 at 15:50
  • $\begingroup$ Actually I know its usage,but I don't know its function in here.No branch be removed in here I think or I miss other something.We can show the result of Pruning. $\endgroup$
    – yode
    Mar 21, 2016 at 16:05
  • $\begingroup$ Oh.I just mean you first solution here ep1 = MorphologicalTransform[Pruning[Thinning@Binarize@i, 150], "SkeletonEndPoints"].In the second solution its behavior in our expectation. $\endgroup$
    – yode
    Mar 21, 2016 at 16:12
  • $\begingroup$ @yode Pruning here removes a tiny branch (which is the source of the trouble). In the case of ep1 this "branch" is of length one pixel as you can see with ImageDifference[Thinning@Binarize@i, Pruning[Thinning@Binarize@i, 150]], but without Pruning we get this pixel as one of the end points even with "SkeletonEndPoints" (check MorphologicalTransform[Thinning@Binarize@i, "SkeletonEndPoints"]). $\endgroup$ Mar 21, 2016 at 17:12
3
$\begingroup$

As Alexey noted in his follow-up question, there appears to be a bug with range-1 filters on binarised images. That means your original code actually was supposed to work. You can indeed fix it by "unbinarising" the image first:

pic = Thinning@Binarize@Import["https://i.stack.imgur.com/K8dm3.png"];
nonBinaryPic = Image[pic, "Real"];

filtered = ImageFilter[If[#[[2, 2]] == 1 && Total[#, 2] == 2, 1, 0] &, nonBinaryPic, 1];
Total[ImageData@filtered, 2]
(* 4. *)

You might have to re-binarise filtered after the computation if you want to end up with a binarised image.

Of course this workaround is somewhat unsatisfactory, but at least it lets you use your original approach, which I'd personally prefer to using a similar approach on the binarised image when it's not even clear why the bug exists in the first place.

$\endgroup$
2
  • $\begingroup$ (+1) You can convert binary image to grayscale simply with Image[pic, "Real"] or Image[pic, "Real32"]. $\endgroup$ Mar 21, 2016 at 14:35
  • $\begingroup$ @AlexeyPopkov Ah, thanks. That's what I was looking for. $\endgroup$ Mar 21, 2016 at 14:36
2
$\begingroup$

Imperfect Branch points often are being caught as "EndPoints". You may filter them out like this:

i   = Thinning@Binarize@Import@"https://i.stack.imgur.com/K8dm3.png";
iep = ImageMultiply[
      ColorNegate@ Dilation[MorphologicalTransform[i, "SkeletonBranchPoints"], 15], 
      MorphologicalTransform[i, "EndPoints"]], 5];

Dilation[iep, 5] (*for visualization *)

Mathematica graphics

$\endgroup$
1
  • $\begingroup$ It is certainly a solution by branch points to filter it.But I just don't really like the 15.I think it will result a inaccurate result in some case,such as thebranch points near to the end points.But I don't very affirm my guess. $\endgroup$
    – yode
    Mar 21, 2016 at 15:07
2
$\begingroup$

By increasing the radius in your ImageFilter from 1 to 2, I'm able to pick up the endpoints via (with Alexey Popkov's help),

ImageFilter[If[#[[3, 3]] == 1 && Total[#, 2] == 3, 1, 0] &, pic, 2]

enter image description here

$\endgroup$
9
  • $\begingroup$ Strictly speaking your approach gives not the end pixels but next-to-end pixels as you can see with ppos=ImageValuePositions[ImageFilter[If[(#[[2,2]]==1||#[[-2,-2]]==1)&&Total[#,2]==2,1,0]&,pic,2],White]; Show[ImageTrim[pic,{#},2],GridLines->{Range[10],Range[10]},ImageSize->100,Method->{"GridLinesInFront"->True}]&/@ppos (output). $\endgroup$ Mar 21, 2016 at 12:41
  • $\begingroup$ Thank you. I assumed there was a better way to get this, but from the OP I took it they want an answer that uses ImageFilter so I just fiddled with it until it gave the apparently right points. $\endgroup$
    – Jason B.
    Mar 21, 2016 at 12:44
  • $\begingroup$ Can you explain why the OP's ImageFilter approach doesn't work? I seem to understand how your method work but still don't see why it shouldn't work with radius 1. $\endgroup$ Mar 21, 2016 at 12:46
  • $\begingroup$ For obtaining the correct end points one can use ImageFilter[If[(#[[3,3]]==1||#[[-3,-3]]==1)&&Total[#,2]==3,1,0]&,pic,2]. $\endgroup$ Mar 21, 2016 at 13:16
  • $\begingroup$ Excellent, thank you. Actually, with that fix you don't need the Or or the second condition there. But for the life of me I can't see why ImageFilter[If[#[[3, 3]] == 1 && Total[#, 2] == 3, 1, 0] &, pic, 2] works when ImageFilter[If[#[[2, 2]] == 1 && Total[#, 2] == 2, 1, 0] &, pic, 1] fails. I put a Print[MatrixForm@#]; statement in the first and it looks like the second form should work also. $\endgroup$
    – Jason B.
    Mar 21, 2016 at 13:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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