11

This is a further question of this post.I just make the path be curve by Photoshop.

enter image description here

I note Mr. C.E.'s method seem to not work anymore.Any method can repair it?

Since I posted the original question, I believe I should provide the curved path image as well. So, here goes an original image with the suggested curved path. Unfortunately, in this case, the sugarcane is still in its infancy. Aparently it should be easier if the sugarcane were more developed. However, this image represents a very real case which should be dealt with. Thanks. FACamargo.

image2

yode
  • 26,686
  • 4
  • 62
  • 167
  • The answer is forthcoming :) – C. E. Sep 23 '16 at 15:47
  • I'm wondering whether one could use the same Fourier transform approach used by @C.E. in the straight lines case, but instead of selecting only the ft max frequencies, one would accept a range of higher frequencies, something like 95% or higher, maybe parameterizing the possible choices with Manipulate in order to verify the results. Well... let's try it... – FACamargo Sep 23 '16 at 15:51
  • @FACamargo I posted my attempt now, this is as far as I've been able to take it. – C. E. Sep 23 '16 at 16:04

3 Answers3

10

I'll work with FACamargo's photo, since this question has also been asked in the original Q&A.

We begin like for that other photo:

img = Import["https://i.stack.imgur.com/RMQ6U.jpg"];
gray = ColorConvert[RemoveAlphaChannel[img], "Grayscale"];
data = ImageData[gray];

ft = Fourier[data];
ft = RotateLeft[ft, Floor[Dimensions[ft]/2]];

scaled = ft // Abs // Log // Rescale // Image

Mathematica graphics

We see that in this case the information is contained in many different frequencies instead of just a few frequencies. It is therefore much more difficult to extract the interesting signal, but by no means impossible. The method isn't broken; just more difficult to apply.

Like in the other answer we need to filter out the really short frequencies because those frequencies only correspond to the background intensity. They don't correspond to features in the image.

We can see that most of the features in the image can be described by six sets of frequencies, mirrored about the center. An appropriate filter might be this:

mask[r1_, r2_] := With[{d = ImageDimensions[img]},
  Graphics[{
    White,
    Disk[d/2, r2, {Pi/2, Pi/2 + Pi/4}],
    Disk[d/2, r2, {Pi + Pi/2, Pi + Pi/2 + Pi/4}],
    Black, Disk[d/2, r1]
    },
   PlotRange -> {{0, First[d]}, {0, Last[d]}},
   ImageSize -> ImageDimensions[img],
   Background -> Black
   ]
  ]
filtered = ImageMultiply[mask[50, 70], scaled]

Mathematica graphics

Picking out the 20 most high intensity frequencies in this filtered Fourier spectrum, we get

norm = Map[Norm, ImageData[filtered], {2}];
pos = Flatten[Position[norm, #] & /@ TakeLargest[Flatten[norm], 20], 1];

invft = InverseFourier[SparseArray[pos -> 1, Dimensions[ft]] ft];
invimg = invft // Abs // Rescale // Image;
ImageMultiply[img, invimg]

Mathematica graphics

Well... that's not nearly as good as in for the other photo, but some information about the lines is still there.

The main problems are:

  1. The pattern is weaker in this image, it doesn't stand out as the only significant pattern.
  2. The information about the pattern is spread out among many different frequencies, not just two frequencies like in the other image. That makes it more difficult to extract the right frequencies.

I don't know at the moment how to find the right frequencies, but this shows that the approach isn't completely invalidated. It's just more difficult to apply it to this photo.

The Fourier transform of Yode's image looks like it might be even more difficult to extract the right frequencies from.

C. E.
  • 70,533
  • 6
  • 140
  • 264
  • 1
    Kudos @C.E.. You've done it again! I will explore your techniques further down, and try to generalize it with other pictures that I have, in order to minimize the shadows over the recovered image. The lines are there, so now we only need to extract them. Good job! – FACamargo Sep 23 '16 at 16:13
8

I have to say the method derive from C.E. totally.I just give a record for reading about how to deal with that picture I have uploaded.

img =Import["https://i.stack.imgur.com/nxMqb.png"];
gray = ColorConvert[RemoveAlphaChannel[img], "Grayscale"];
data = ImageData[gray];

ft = Fourier[data*PadRight[{{}}, Dimensions[data], {{1, -1}, {-1, 1}}]];

scaled = ImagePeriodogram[gray]

Then I get a mask image by Image-Tool.

This is what I make,you can run mask=Normal[Databin["fWADf47A",{1}]][[1,1]] to get it exactly.

Select the frequency that we need.

select = MaxDetect[ImageMultiply[scaled, ColorNegate[mask]], .1]

Almost.

pos = SparseArray[ImageData[select]]["NonzeroPositions"];
invft = InverseFourier[SparseArray[pos -> 1, Dimensions[ft]] ft];
{invimg = invft // Abs // Rescale // Image, 
 ImageMultiply[invimg, img]}

But there are some dissatisfaction in this result about that shadows.I will glad to see anyone can improve it still.


Update(little improvements):

{bin = Binarize[TopHatTransform[invimg, 1] // ImageAdjust, .1], 
 HighlightImage[img, bin]}


Update 2(As the comment from Silvia): The LocalAdaptiveBinarize will give a better result indeed.

Thinning@LocalAdaptiveBinarize[invimg, 6]

enter image description here

yode
  • 26,686
  • 4
  • 62
  • 167
  • 1
    I like the way you compute select and pos, nice. +1 – C. E. Sep 23 '16 at 23:04
  • 1
    @C.E. It's my honour to get you such comment. :) – yode Sep 23 '16 at 23:06
  • 1
    The next question, to both of you, will be "how to extract the actual lines geometry from the inverse transformed image?". I believe that it will not be too difficult to just "follow the black pixels" after some thinning and prunning, but I will first mix and match both approaches in a kind of tool that allows one to "play" with parameters for picking the right frequencies in a more visual way... I promise to publish it here within the coming week. For the moment I must say that I am impressed! – FACamargo Sep 23 '16 at 23:45
  • 1
    @C.E. How about this little new change?Hope to help. – yode Sep 24 '16 at 00:04
  • 1
    @yode I like my version better, but it's a matter of taste, – C. E. Sep 24 '16 at 01:47
  • @FACamargo I look forward to seeing what you can achieve :) – C. E. Sep 24 '16 at 01:47
  • @yode, I followed your steps with my own image, and the results were not as good as your own. Would you care to try that yourself?

    I have also another question to you and C.E., which I'll make in the next comment.

    – FACamargo Sep 24 '16 at 12:24
  • @C.E. and Yode, I am trying to understand the rationale for multiplying the sparse array by ft in the formula invft = InverseFourier[SparseArray[pos -> 1, Dimensions[ft]] ft]; This question came to my mind already in the case for the straight lines, and I tried removing the ft term and the changes (there) were almost unnoticeable. I tried removing ft again here, and again it were not clear why you did that. Certainly the image becomes clearer with the multiplication, but I'm not sure why. Would you care to comment? I tried to think of a Sparse array not so sparse, and it really confuses me.Tnx – FACamargo Sep 24 '16 at 12:34
  • The point (in the previous comment) is that it is obvious that the multiplication of the two matrices will be including many more frequencies from the "columns" of ft into the "rows" of inverse transform, and that is alright. However, it also means that some frequencies can potentially be mixed (added together) if pos has elements in the same row, and that is what confuses me. There are many years already that I studied FT, and I might be missing something here, for which I beg for your help. Tnx so much. – FACamargo Sep 24 '16 at 12:44
  • @FACamargo Sorry for my reading ability. :) – yode Sep 24 '16 at 13:01
  • Don't worry @yode. I know the feeling... :) :) :) – FACamargo Sep 24 '16 at 13:08
  • 1
    @FACamargo SparseArray[pos -> 1] is a matrix of zeros and ones so multiplying this matrix by ft has the effect of selecting the elements of the Fourier transform that correspond to the frequencies we want. Some information about the original image is contained in the positions of the elements, some information is contained in the size of the elements, and some information is contained in the angle of the element (it has an angle because it is a complex number.) Try taking the Fourier transform of an image, take Abs, and then do the inverse transform. It does not work well in general. – C. E. Sep 24 '16 at 13:13
  • That's all very clear to me @C.E.. My problem is that SparseArray ends up selecting much more that just the frequencies at pos, but actually it selects a whole row of frequencies. In the case of the straight lines, please, do try to remove ft from the product, and you'll see that you get almost exactly the same picture. In the case of the curved lines pos selects many more frequencies, and might end up adding them together whenever there are many '1' in the same row... Imagine if SparseArray were all ones: the product by ft would not be ft itself, but a weird sum of the frequencies, isn't it? – FACamargo Sep 24 '16 at 13:40
  • 1
    @FACamargo How can I check for myself that it selects "a whole row of frequencies"? If I evaluate (SparseArray[pos -> 1, Dimensions[ft]] ft)["NonzeroPositions"] it returns {{207, 254}, {251, 204}}, as expected for the first image. For the image with the curved lines, I note that Length[(SparseArray[pos -> 1, Dimensions[ft]] ft)["NonzeroPositions"]] is 20, also as expected. – C. E. Sep 24 '16 at 14:14
  • @C.E., apparently I was mistaken: I had though that (SparseArray[] ft) was multiplying the two matrices (dot-product). But it is actually doing an element-wise multiplication. In that regard I find Mathematica very challenging, for it doesn't behave always like real math. But, I go on learning a bit more everyday. Thank you so much. – FACamargo Sep 24 '16 at 14:42
  • @yode, could you kindly explain me this line: ft = Fourier[data*PadRight[{{}}, Dimensions[data], {{1, -1}, {-1, 1}}]]; ?? Why did you make this {{1,-1,1,-1...}} array and multiplied "data" by it before computing ft? Thank you.. – FACamargo Sep 25 '16 at 17:27
  • @FACamargo Do the same thing with RotateLeft[ft, Floor[Dimensions[ft]/2]] – yode Sep 25 '16 at 17:42
  • But +1,-1? Why @yode? – FACamargo Sep 25 '16 at 17:44
  • 1
    @FACamargo Sorry,I don't very clearly about it actually. – yode Sep 25 '16 at 17:56
  • 2
    LocalAdaptiveBinarize on your invimg might give a better result. – Silvia Oct 01 '16 at 11:15
  • @Silvia Thanks for your advice. – yode Oct 01 '16 at 12:04
1

Make the answer above more readable:

img = Import["https://i.stack.imgur.com/nxMqb.png"];
gray = ColorConvert[RemoveAlphaChannel[img], "Grayscale"];
data = ImageData[gray];

ftshift = ResourceFunction["FourierShift"][Fourier[data]]; select = MaxDetect[ImageMultiply[ImagePeriodogram[gray], ColorNegate[mask]],0.1](mask is same as the above answer)

enter image description here

result = 
  ImageAdjust[
   Image[Abs[
     InverseFourier[
      ResourceFunction["FourierShiftInverse"][
       ftshift*ImageData[select]]]]]];
hight = HighlightImage[
   img, {RGBColor[1, 1, 0, 0.8], EdgeForm[], 
    Thinning@LocalAdaptiveBinarize[result, 5]}];
ImageAssemble[ConformImages[{result, hight}], Spacings -> 5, 
 Background -> 1]

enter image description here

yode
  • 26,686
  • 4
  • 62
  • 167