4

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?

yode
  • 26,686
  • 4
  • 62
  • 167

4 Answers4

3

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

Alexey Popkov
  • 61,809
  • 7
  • 149
  • 368
  • I'm really don't know what the Pruning do.But it will lead to a right result. Can you give a explaination? – yode Mar 21 '16 at 15:45
  • @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. – Alexey Popkov Mar 21 '16 at 15:50
  • 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. – yode Mar 21 '16 at 16:05
  • 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. – yode Mar 21 '16 at 16:12
  • @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"]). – Alexey Popkov Mar 21 '16 at 17:12
  • @yode I just have found that MorphologicalTransform[Pruning[Thinning@Binarize@i, 1], "EndPoints"] is actually quite sufficient. I'll update the answer with this (much more efficient) solution. – Alexey Popkov Mar 21 '16 at 17:22
  • Thanks for your ImageDifference.If not,I have to find a high magnification.:) – yode Mar 21 '16 at 17:37
3

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.

Martin Ender
  • 8,774
  • 1
  • 34
  • 60
2

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

Jason B.
  • 68,381
  • 3
  • 139
  • 286
  • 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). – Alexey Popkov Mar 21 '16 at 12:41
  • 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. – Jason B. Mar 21 '16 at 12:44
  • 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. – Alexey Popkov Mar 21 '16 at 12:46
  • For obtaining the correct end points one can use ImageFilter[If[(#[[3,3]]==1||#[[-3,-3]]==1)&&Total[#,2]==3,1,0]&,pic,2]. – Alexey Popkov Mar 21 '16 at 13:16
  • 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. – Jason B. Mar 21 '16 at 13:24
  • I have created separate question on this issue. – Alexey Popkov Mar 21 '16 at 13:44
  • We cannot find this end points when the radius is 2 and total is 3. – yode Mar 21 '16 at 16:10
  • @yode - but you wouldn't find that point if the radius was 1 and the total was 2 either.... – Jason B. Mar 21 '16 at 16:23
  • @JasonB Ok.Sounds reasonable.Thinks for your interesting solution. – yode Mar 21 '16 at 16:26
2

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

Alexey Popkov
  • 61,809
  • 7
  • 149
  • 368
Dr. belisarius
  • 115,881
  • 13
  • 203
  • 453
  • 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. – yode Mar 21 '16 at 15:07