ANSWER LINES COUNT - PART III
1.- Initially the RGB image has been translated into single layer with
A(:,:,1)+A(:,:,2)+A(:,:,3)
Some may consider that the black & white CCIR correction may be a better option
A42=.299*A(:,:,1)+.587*A(:,:,2)+.114*A(:,:,3);
figure(3);imshow(42);
2.- threshold with imcontrast
figure(2);h1=imshow(A(:,:,1)+A(:,:,2)+A(:,:,3));imcontrast(h1)
figure(1);h2=imshow(A(:,:,1));imcontrast(h2)
since h2 the output handle of imcontrast when called as h2=imcontrast(h1) does not point to the resulting image, (it points to the imconstrast display, but what percentage of imcontrast users really need a handle to the GUI compared to the processed image?) the second contrast windowing with [146 147] is equivalent to for instance imcontrast [235 237] on the initial A(:,:,1)+A(:,:,2)+A(:,:,3)
3.- to actually obtain the resulting image either multithresh and imquantize
or
level = multithresh(A2,2);
both show quite same image
seg_A2=imquantize(A2,level);
figure(3);imshow(seg_A2,[])
4.- or simply use imshow that for this particular case it gets to same place as imcontrast, multithresh or quantize. I tried
figure(4);imshow(A2,[135 137])
figure(5);imshow(A42,[125 126])
figure(6);imshow(A42,[135 137])
5.- focus on single RGB layer and narrow image to ribbon: In this case less data is more information. So instead of using all RGB layers, the red layer alone seems to have more contrast than using all the available data. Observe the central patch, where in the original image looks like a Cesna crashed, no trees: with A(:,:,1) such patch is pitch black while using all 3 RGB layers, either with CCIR correction or adding them up, image noise in that area increases.
Let's focuss on left hand side ribbon, just before the Cesna crash like hole starts, and note that we are all the time counting shadows, not the tree lines themselves as their shadows are more consistent:
D=imread('ribbon.jpg')
[sz1D sz2D sz3D]=size(D)
sz1D =
484.00
sz2D =
165.00
sz3D =
3.00
resamp = makeresampler({'nearest','cubic'},'fill');
stretch = maketform('affine',[1.8 0; 0 1.8; 0 0]);
D2 = imtransform(D(:,:,1),stretch,resamp);
[sz1D2 sz2D2 sz3D2]=size(D2)
sz1D2 =
871.00
sz2D2 =
297.00
sz3D2 =
1.00
figure(2);imshow(D2);
6.- Simplify: ignore tree lines tilt angle. A single line is
the angle between the sample line and the tree lines (as long as 'far enough' from radon rays running parallel ~ theta>45º) doesn't really matter.
7.- radon: Since R2016 release, the Image Processing Toolbox is included in MATLAB base pack, which is great news, because in the past some ocasional MATLAB users seeking image processing solutions, a one-off visit to MATLAB, were put off by not being able to use IP Toolbox functions on the MATLAB package, any one has a chance to test drive the IP Toolbox.
radon basics:
In this case there are 2 ways to use radon • parallel to the tree lines, theta~80º • perpendicular to the tree lines, theta~-20º
the variation (jitter) of spacing between adjacent 'tree lines' is enough to spoil radon on the narrower ribbon, have a look, run the following lines, and press for instance Enter until all angles plotted. Place the plot figure besides MATLAB window so you can see what happens:
theta=[70:.1:80];
[R,xp]=radon(D2,theta)
[sz1R sz2R]=size(R)
for k=1:sz2R
plot(R(:,k));grid on
disp(k);
str=input(' ','s');
end
And this was on the narrow ribbon. No way to get a clear comb with one and only one sharp peak for each tree line.
radon applied to the main image is worse, no matter colour, BW, BW CCIR, single RGB layer: while you may catch a long burst of pulses, there are long enough sections of the sample line that render peak counting useless. Have a look:
8.- radon on ribbon with horizontal tree lines:
We can appreciate that while the top tree lines have slight positive slope, the tree lines on the lower portion of the ribbon have negative slope, the flying platform carrying the camera was probably turning left when the photo was taken.
Really, radon seems an extremely useful function, but in this particular case, at least to me, it make more sense to count peaks along single lines running perpendicular to the tree lines.
9.- radon on even narrower ribbon:
theta=[85:.1:95];
P=imread('ribbon_02_narrower.jpg');
[R,xp]=radon(P(:,:,1),theta);
[sz1R sz2R]=size(R)
for k=1:sz2R
plot(R(:,k));grid on
disp(k);
str=input(' ','s');
end
theta =86.5º
theta = 90º theta close to 90º
Now we have clear sharp peaks, apparently one per tree line, or so we think:
theta=90º is k=50. Clipping
L1=R(:,50)'
L1(L1>2000)=2000
[pks,locs]=findpeaks(L1)
plot(L1,'LineWidth',1.5,'Color','b');grid on
hold all;plot(locs,pks,'x','Color','r')
ax=gca
ax.YLim=[0 2100]
now remove false peaks
pks2=pks(pks>1600)
numel(pks2)
=
48.00
It looks good, but what if the chosen sample line suffers a few more gaps like this one?
On the other hand, DTFT on a sample line with a few missing peaks is more robust, and will still show a frequency peak on the right frequency. This is why DTFT, in this particular case, is a better choice.
10.- So, DTFT line 20 of the ribbon, no need to have horizontal tree lines.
L1=D2(:,20)
length(L1)
=
871
r_L1=xcorr(L1);r_L12=r_L1([floor(length(r_L1)/2):end])
x=r_L12';n=[1:1:length(r_L12)]
k=0:500;w=(pi/500)*k
X=x*(exp(-j*pi/500)).^(n'*k)
X=x*(exp(-j*pi/500)).^(n'*k)
magX=abs(X);plot(magX)
since we are after the cycle of a simple tone, expected around 100 on the previous graph, then let's find the exact position within such narrower frequency range
[pks_magX loc_pks_magX]=findpeaks(magX([90:110]),[90:110])
loc_pks_magX(find(pks_magX==max(pks_magX)))
pks_magX =
1.0e+08 *
Columns 1 through 4
0.031378416088323 0.021018552598383 2.416607712099700 0.584907695507022
Column 5
0.598161849635904
loc_pks_magX =
92 94 102 106 108
the first peak above DC
f_peak=loc_pks_magX(find(pks_magX==max(pks_magX)))
=
102
with this DTFT Fs=
numel(magX)
=
501
the maximum frequency that could occur along a sample line is floor(numel(L1)/2). So the answer is
f_peak/numel(magX)*numel(L1)/2
ans =
49.547904191616766
which is quite Carling, isn't it?
Following, different attempts to improve accuracy.
On the following graph, the 1st peak above DC, the one with the marker on in previous DTFT graph is not sharp enough, moving the marker around the peak you notice there are at least 2 samples with quite similar value.
11.- Attempting to improve result (see 50.0) just increasing
frequency resolution is not going to work:
k=0:2500;w=(pi/2500)*k
X=x*(exp(-j*pi/2500)).^(n'*k)
X=x*(exp(-j*pi/2500)).^(n'*k)
magX=abs(X)
plot(magX); grid on
[pks_magX loc_pks_magX]=findpeaks(magX([260:310]),[260:310])
f_peak=loc_pks_magX(find(pks_magX==max(pks_magX)))
=
284
f_peak/numel(magX)*numel(L1)/2
=
49.453018792483007
Again, because the part I single line had 1665 samples, the spectrum is more accurate than previously, yet is there really need for such added accuracy?
12.-more wood, further interpolating
resamp = makeresampler({'nearest','cubic'},'fill');
stretch = maketform('affine',[5.1 0; 0 5.1; 0 0]);
D3 = imtransform(D(:,:,1),stretch,resamp);
imshow(D3)
imstransform stops updating the pixel coordinates, displaying a decimated version, and not even updating the size of the picture. But the warning message has a way around:
Warning: IMTRANSFORM has increased the scale of your output pixels in X-Y space to keep
the dimensions of the output image from getting too large. This automatic scale change
will be removed in a future release.
To ensure that output pixel scale matches input pixel scale specify the 'XYScale'
parameter. For example, call IMTRANSFORM like this:
B = IMTRANSFORM(A,T,'XYScale',1)
> In imtransform>compute_xyscale (line 323)
In imtransform>parse_inputs (line 463)
In imtransform (line 265)
Warning: Image is too big to fit on screen; displaying at 67
> In images.internal.initSize (line 71)
In imshow (line 309)
add: 'XYScale',1 increasing the value of the XYScale actually reduces the size of the output image that is supposed to be larger, not smaller
resamp = makeresampler({'nearest','cubic'},'fill');
stretch = maketform('affine',[5.1 0; 0 5.1; 0 0]);
D3 = imtransform(D(:,:,1),stretch,resamp,'XYScale',1);
L1=D3(:,100)
plot(L1);grid on
r_L1=xcorr(L1)
r_L12=r_L1([floor(length(r_L1)/2):end])
x=r_L12';n=[1:1:length(r_L12)]
r_L1=xcorr(L1)
r_L12=r_L1([floor(length(r_L1)/2):end])
k=0:2500;w=(pi/2500)*k
X=x*(exp(-j*pi/2500)).^(n'*k)
X=x*(exp(-j*pi/2500)).^(n'*k)
magX=abs(X)
plot(magX); grid on
[pks_magX loc_pks_magX]=findpeaks(magX([95:105]),[95:105])
f_peak=loc_pks_magX(find(pks_magX==max(pks_magX)))
f_peak/numel(magX)*numel(L1)/2
=
101
f_peak/numel(magX)*numel(L1)/2
=
49.773090763694526
k=0:2500;w=(pi/2500)*k
X=x*(exp(-j*pi/2500)).^(n'*k)
X=x*(exp(-j*pi/2500)).^(n'*k)
magX=abs(X)
plot(magX); grid on
[pks_magX loc_pks_magX]=findpeaks(magX([95:105]),[95:105])
f_peak=loc_pks_magX(find(pks_magX==max(pks_magX)))
f_peak/numel(magX)*numel(L1)/2
s=15500
[590:650]
=
49.296819560028389
13.- FFT, sharper peak
magX=abs(fft(r_L12))
plot(magX); grid on
50/(2466/2)*2465/2
=
49.979724249797236
14.- PLOMB
[Pxx,F]=plomb(x,n)
197/4932*2465/2
=
49.230028386050279
15.- tried different DTFT values dtft_range=[500:500:2500]
f_peaks_a=zeros(numel(dtft_range,sz1D2))
for s=[1:1:numel(dtft_range)]
for k=1:sz1D2
L1=D2(:,k)
r_L1=xcorr(L1)
r_L12=r_L1([floor(length(r_L1)/2):end])
k=0:(s*500);w=(pi/(s*500))*k
X=x*(exp(-j*pi/(s*500))).^(n'*k)
X=x*(exp(-j*pi/(s*500))).^(n'*k)
magX=abs(X)
plot(magX); grid on
magX=abs(X);
[pks_magX loc_pks_magX]=findpeaks(magX([90:110]))
end
end
So it is reasonable to answer that on vertical sample line 20 there are 49 tree lines.
Certainty statements are beyond the scope of this QA. But, at least in this case, the spectral basic analysis of the autocorrelation of a single line hatt has many missing peaks (tree lines) is more robust than radon.
So this is it: 49.5 tree lines on a single count line is as good as it gets, and if you think it twice, it may be that there is half cycle lost on either head or tail of the vertical sample line.
Other spectrum analysis tools:
16.- spectrogram, can we spot the frequency variation?
spectrogram(r_L12,'yaxis')
17.- periodogram directly on sample line L1
18.- periodogram on r_L1