source: trunk/src/civ.m @ 1165

Last change on this file since 1165 was 1163, checked in by sommeria, 2 months ago

SearchBoxSize? changed to SearchRange? in civ, possibility of a grid set by a netcdf file

File size: 17.8 KB
Line 
1%--------------------------------------------------------------------------
2%  'civ': function  adapted from PIVlab http://pivlab.blogspot.com/
3% function [xtable ytable utable vtable typevector] = civ (image1,image2,ibx,iby step, subpixfinder, mask, roi)
4%
5% OUTPUT:
6% xtable: set of x coordinates
7% ytable: set of y coordinates
8% utable: set of u displacements (along x)
9% vtable: set of v displacements (along y)
10% ctable: max image correlation for each vector
11% typevector: set of flags, =1 for good, =0 for NaN vectors
12%
13%INPUT:
14% par_civ: structure of input parameters, with fields:
15%  .ImageA: first image for correlation (matrix)
16%  .ImageB: second image for correlation(matrix)
17%  .CorrBoxSize: 1,2 vector giving the size of the correlation box in x and y
18%  .SearchBoxSize:  1,2 vector giving the size of the search box in x and y
19%  .SearchBoxShift: 1,2 vector or 2 column matrix (for civ2) giving the shift of the search box in x and y
20%  .CorrSmooth: =1 or 2 determines the choice of the sub-pixel determination of the correlation max
21%  .ImageWidth: nb of pixels of the image in x
22%  .Dx, Dy: mesh for the PIV calculation
23%  .Grid: grid giving the PIV calculation points (alternative to .Dx .Dy): centres of the correlation boxes in Image A
24%  .Mask: name of a mask file or mask image matrix itself
25%  .MinIma: thresholds for image luminosity
26%  .MaxIma
27%  .CheckDeformation=1 for subpixel interpolation and image deformation (linear transform)
28%  .DUDX: matrix of deformation obtained from patch at each grid point
29%  .DUDY
30%  .DVDX:
31%  .DVDY
32
33function [xtable,ytable,utable,vtable,ctable,FF,result_conv,errormsg] = civ (par_civ)
34
35%% check input images
36par_civ.ImageA=sum(double(par_civ.ImageA),3);%sum over rgb component for color images
37par_civ.ImageB=sum(double(par_civ.ImageB),3);
38[npy_ima,npx_ima]=size(par_civ.ImageA);
39if ~isequal(size(par_civ.ImageB),[npy_ima npx_ima])
40    errormsg='image pair with unequal size';
41    return
42end
43
44%% prepare measurement grid if not given as input
45if ~isfield(par_civ,'Grid')% grid points defining central positions of the sub-images in image A
46%     if ischar(par_civ.Grid)%read the grid file if the input is a file name (grid in x, y image coordinates)
47%         par_civ.Grid=dlmread(par_civ.Grid);
48%         par_civ.Grid(1,:)=[];%the first line must be removed (heading in the grid file)
49%     end
50%     % else par_civ.Grid is already an array, no action here
51% else% automatic grid in x, y image coordinates
52    nbinterv_x=floor((npx_ima-1)/par_civ.Dx);
53    gridlength_x=nbinterv_x*par_civ.Dx;
54    minix=ceil((npx_ima-gridlength_x)/2);
55    nbinterv_y=floor((npy_ima-1)/par_civ.Dy);
56    gridlength_y=nbinterv_y*par_civ.Dy;
57    miniy=ceil((npy_ima-gridlength_y)/2);
58    [GridX,GridY]=meshgrid(minix:par_civ.Dx:npx_ima-1,miniy:par_civ.Dy:npy_ima-1);
59    par_civ.Grid(:,1)=reshape(GridX,[],1);
60    par_civ.Grid(:,2)=reshape(GridY,[],1);% increases with array index
61end
62nbvec=size(par_civ.Grid,1);
63
64
65%% prepare correlation and search boxes
66CorrBoxSizeX=par_civ.CorrBoxSize(:,1);
67CorrBoxSizeY=par_civ.CorrBoxSize(:,2);
68if size(par_civ.CorrBoxSize,1)==1
69    CorrBoxSizeX=par_civ.CorrBoxSize(1)*ones(nbvec,1);
70    CorrBoxSizeY=par_civ.CorrBoxSize(2)*ones(nbvec,1);
71end
72
73shiftx=par_civ.SearchBoxShift(:,1);%use the input shift estimate, rounded to the next integer value
74shifty=-par_civ.SearchBoxShift(:,2);% sign minus because image j index increases when y decreases
75if numel(shiftx)==1% case of a unique shift for the whole field( civ1)
76    shiftx=shiftx*ones(nbvec,1);
77    shifty=shifty*ones(nbvec,1);
78end
79
80%% shift the grid by half the expected displacement to get the velocity closer to the initial grid
81par_civ.Grid(:,1)=par_civ.Grid(:,1)-shiftx/2;
82par_civ.Grid(:,2)=par_civ.Grid(:,2)+shifty/2;
83
84%% Array initialisation and default output  if par_civ.CorrSmooth=0 (just the grid calculated, no civ computation)
85xtable=round(par_civ.Grid(:,1)+0.5)-0.5;
86ytable=round(npy_ima-par_civ.Grid(:,2)+0.5)-0.5;% y index corresponding to the position in image coordinates
87shiftx=round(shiftx);
88shifty=round(shifty);
89utable=shiftx;%zeros(nbvec,1);
90vtable=shifty;%zeros(nbvec,1);
91ctable=zeros(nbvec,1);
92FF=zeros(nbvec,1);
93result_conv=[];
94errormsg='';
95
96%% prepare mask
97if isfield(par_civ,'Mask') && ~isempty(par_civ.Mask)
98    if strcmp(par_civ.Mask,'all')
99        return    % get the grid only, no civ calculation
100    elseif ischar(par_civ.Mask)
101        par_civ.Mask=imread(par_civ.Mask);% read the mask if not allready done
102    end
103end
104check_MinIma=isfield(par_civ,'MinIma');% test for image luminosity threshold
105check_MaxIma=isfield(par_civ,'MaxIma') && ~isempty(par_civ.MaxIma);
106
107%% Apply mask
108% Convention for mask, IDEAS NOT IMPLEMENTED
109% mask >200 : velocity calculated
110%  200 >=mask>150;velocity not calculated, interpolation allowed (bad spots)
111% 150>=mask >100: velocity not calculated, nor interpolated
112%  100>=mask> 20: velocity not calculated, impermeable (no flux through mask boundaries)
113%  20>=mask: velocity=0
114checkmask=0;
115MinA=min(min(par_civ.ImageA));
116%MinB=min(min(par_civ.ImageB));
117%check_undefined=false(size(par_civ.ImageA));
118if isfield(par_civ,'Mask') && ~isempty(par_civ.Mask)
119    checkmask=1;
120    if ~isequal(size(par_civ.Mask),[npy_ima npx_ima])
121        errormsg='mask must be an image with the same size as the images';
122        return
123    end
124    check_undefined=(par_civ.Mask<200 & par_civ.Mask>=20 );
125end
126
127%% compute image correlations: MAINLOOP on velocity vectors
128corrmax=0;
129sum_square=1;% default
130mesh=1;% default
131CheckDeformation=isfield(par_civ,'CheckDeformation')&& par_civ.CheckDeformation==1;
132if CheckDeformation
133    mesh=0.25;%mesh in pixels for subpixel image interpolation (x 4 in each direction)
134    par_civ.CorrSmooth=2;% use SUBPIX2DGAUSS (take into account more points near the max)
135end
136 
137if par_civ.CorrSmooth~=0 % par_civ.CorrSmooth=0 implies no civ computation (just input image and grid points given)
138    for ivec=1:nbvec
139        iref=round(par_civ.Grid(ivec,1)+0.5);% xindex on the image A for the middle of the correlation box
140        jref=round(npy_ima-par_civ.Grid(ivec,2)+0.5);%  j index  for the middle of the correlation box in the image A
141        FF(ivec)=0;
142        ibx2=floor(CorrBoxSizeX(ivec)/2);
143iby2=floor(CorrBoxSizeY(ivec)/2);
144isx2=ibx2+ceil(par_civ.SearchRange(1));
145isy2=iby2+ceil(par_civ.SearchRange(2));
146        subrange1_x=iref-ibx2:iref+ibx2;% x indices defining the first subimage
147        subrange1_y=jref-iby2:jref+iby2;% y indices defining the first subimage
148        subrange2_x=iref+shiftx(ivec)-isx2:iref+shiftx(ivec)+isx2;%x indices defining the second subimage
149        subrange2_y=jref+shifty(ivec)-isy2:jref+shifty(ivec)+isy2;%y indices defining the second subimage
150        image1_crop=MinA*ones(numel(subrange1_y),numel(subrange1_x));% default value=min of image A
151        image2_crop=MinA*ones(numel(subrange2_y),numel(subrange2_x));% default value=min of image A
152        check1_x=subrange1_x>=1 & subrange1_x<=npx_ima;% check which points in the subimage 1 are contained in the initial image 1
153        check1_y=subrange1_y>=1 & subrange1_y<=npy_ima;
154        check2_x=subrange2_x>=1 & subrange2_x<=npx_ima;% check which points in the subimage 2 are contained in the initial image 2
155        check2_y=subrange2_y>=1 & subrange2_y<=npy_ima;
156        image1_crop(check1_y,check1_x)=par_civ.ImageA(subrange1_y(check1_y),subrange1_x(check1_x));%extract a subimage (correlation box) from image A
157        image2_crop(check2_y,check2_x)=par_civ.ImageB(subrange2_y(check2_y),subrange2_x(check2_x));%extract a larger subimage (search box) from image B
158        if checkmask
159            mask1_crop=ones(numel(subrange1_y),numel(subrange1_x));% default value=1 for mask
160            mask2_crop=ones(numel(subrange2_y),numel(subrange2_x));% default value=1 for mask
161            mask1_crop(check1_y,check1_x)=check_undefined(subrange1_y(check1_y),subrange1_x(check1_x));%extract a mask subimage (correlation box) from image A
162            mask2_crop(check2_y,check2_x)=check_undefined(subrange2_y(check2_y),subrange2_x(check2_x));%extract a mask subimage (search box) from image B
163            sizemask=sum(sum(mask1_crop))/(numel(subrange1_y)*numel(subrange1_x));%size of the masked part relative to the correlation sub-image
164            if sizemask > 1/2% eliminate point if more than half of the correlation box is masked
165                FF(ivec)=1; %
166                utable(ivec)=NaN;
167                vtable(ivec)=NaN;
168            else
169                image1_crop=image1_crop.*~mask1_crop;% put to zero the masked pixels (mask1_crop='true'=1)
170                image2_crop=image2_crop.*~mask2_crop;
171                image1_mean=mean(mean(image1_crop))/(1-sizemask);
172                image2_mean=mean(mean(image2_crop))/(1-sizemask);
173            end
174        else
175            image1_mean=mean(mean(image1_crop));
176            image2_mean=mean(mean(image2_crop));
177        end
178        %threshold on image minimum
179        if FF(ivec)==0
180            if check_MinIma && (image1_mean < par_civ.MinIma || image2_mean < par_civ.MinIma)
181                FF(ivec)=1;
182                %threshold on image maximum
183            elseif check_MaxIma && (image1_mean > par_civ.MaxIma || image2_mean > par_civ.MaxIma)
184                FF(ivec)=1;
185            end
186            if FF(ivec)==1
187                utable(ivec)=NaN;
188                vtable(ivec)=NaN;
189            else
190                %mask
191                if checkmask
192                    image1_crop=(image1_crop-image1_mean).*~mask1_crop;%substract the mean, put to zero the masked parts
193                    image2_crop=(image2_crop-image2_mean).*~mask2_crop;
194                else
195                    image1_crop=(image1_crop-image1_mean);
196                    image2_crop=(image2_crop-image2_mean);
197                end
198                %deformation
199                if CheckDeformation
200                    xi=(1:mesh:size(image1_crop,2));
201                    yi=(1:mesh:size(image1_crop,1))';
202                    [XI,YI]=meshgrid(xi-ceil(size(image1_crop,2)/2),yi-ceil(size(image1_crop,1)/2));
203                    XIant=XI-par_civ.DUDX(ivec)*XI+par_civ.DUDY(ivec)*YI+ceil(size(image1_crop,2)/2);
204                    YIant=YI+par_civ.DVDX(ivec)*XI-par_civ.DVDY(ivec)*YI+ceil(size(image1_crop,1)/2);
205                    image1_crop=interp2(image1_crop,XIant,YIant);
206                    image1_crop(isnan(image1_crop))=0;
207                    xi=(1:mesh:size(image2_crop,2));
208                    yi=(1:mesh:size(image2_crop,1))';
209                    image2_crop=interp2(image2_crop,xi,yi,'*spline');
210                    image2_crop(isnan(image2_crop))=0;
211                end
212                sum_square=sum(sum(image1_crop.*image1_crop));
213                %reference: Oliver Pust, PIV: Direct Cross-Correlation
214                %%%%%% correlation calculation
215                result_conv= conv2(image2_crop,flip(flip(image1_crop,2),1),'valid');
216                %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
217                corrmax= max(max(result_conv));
218       
219                %result_conv=(result_conv/corrmax); %normalize, peak=always 255
220                %Find the correlation max, at 255
221                [y,x] = find(result_conv==corrmax,1);
222                subimage2_crop=image2_crop(y:y+2*iby2/mesh,x:x+2*ibx2/mesh);%subimage of image 2 corresponding to the optimum displacement of first image
223                sum_square=sum_square*sum(sum(subimage2_crop.*subimage2_crop));% product of variances of image 1 and 2
224                sum_square=sqrt(sum_square);% srt of the variance product to normalise correlation
225                if ~isempty(y) && ~isempty(x)
226                    try
227                        if par_civ.CorrSmooth==1
228                            [vector,FF(ivec)] = SUBPIXGAUSS (result_conv,x,y);
229                        elseif par_civ.CorrSmooth==2
230                            [vector,FF(ivec)] = SUBPIX2DGAUSS (result_conv,x,y);
231                        else
232                            [vector,FF(ivec)] = quadr_fit(result_conv,x,y);
233                        end
234                        utable(ivec)=vector(1)*mesh+shiftx(ivec);
235                        vtable(ivec)=-(vector(2)*mesh+shifty(ivec));
236                        xtable(ivec)=iref+utable(ivec)/2-0.5;% convec flow (velocity taken at the point middle from imgae 1 and 2)
237                        ytable(ivec)=jref+vtable(ivec)/2-0.5;% and position of pixel 1=0.5 (convention for image coordinates=0 at the edge)
238                        iref=round(xtable(ivec)+0.5);% nearest image index for the middle of the vector
239                        jref=round(ytable(ivec)+0.5);
240                        % eliminate vectors located in the mask
241                        if  checkmask && (iref<1 || jref<1 ||iref>npx_ima || jref>npy_ima ||( par_civ.Mask(jref,iref)<200 && par_civ.Mask(jref,iref)>=100))
242                            utable(ivec)=0;
243                            vtable(ivec)=0;
244                            FF(ivec)=1;
245                        end
246                        ctable(ivec)=corrmax/sum_square;% correlation value
247                    catch ME
248                        FF(ivec)=1;
249                        disp(ME.message)
250                    end
251                else
252                    FF(ivec)=1;
253                end
254            end
255        end
256    end
257end
258ytable=npy_ima-ytable+1;%reverse from j index to image coordinate y
259result_conv=result_conv*corrmax/sum_square;% keep the last (not normalised) correlation matrix for output
260
261
262
263%------------------------------------------------------------------------
264% --- Find the maximum of the correlation function with subpixel resolution
265% make a fit with a gaussian curve from the three correlation values across the max, along each direction.
266% OUPUT:
267% vector = optimum displacement vector with subpixel correction
268% FF =flag: =0 OK
269%           =1 , max too close to the edge of the search box (1 pixel margin)
270% INPUT:
271% result_conv: 2D correlation fct
272% x,y: position of the maximum correlation at integer values
273
274function [vector,FF] = SUBPIXGAUSS (result_conv,x,y)
275%------------------------------------------------------------------------
276% vector=[0 0]; %default
277FF=true;% error flag for vector truncated by the limited search box
278[npy,npx]=size(result_conv);
279
280peaky = y; peakx=x;
281if y < npy && y > 1 && x < npx-1 && x > 1
282   FF=false; % no error by the limited search box
283    max_conv=result_conv(y,x);% max correlation
284    %peak2noise= max(4,max_conv/std(reshape(result_conv,1,[])));% ratio of max conv to standard deviation of correlations (estiamtion of noise level), set to value 4 if it is too low
285    peak2noise=100;% TODO: make this threshold more precise, depending on the image noise
286    result_conv=result_conv*peak2noise/max_conv;% renormalise the correlation with respect to the noise
287    result_conv(result_conv<1)=1; %set to 1 correlation values smaller than 1  (=0 by discretisation, to avoid divergence in the log)
288   
289    f0 = log(result_conv(y,x));
290    f1 = log(result_conv(y-1,x));
291    f2 = log(result_conv(y+1,x));
292    peaky = peaky+ (f1-f2)/(2*f1-4*f0+2*f2);
293    f1 = log(result_conv(y,x-1));
294    f2 = log(result_conv(y,x+1));
295    peakx = peakx+ (f1-f2)/(2*f1-4*f0+2*f2);
296end
297
298vector=[peakx-floor(npx/2)-1 peaky-floor(npy/2)-1];
299
300%------------------------------------------------------------------------
301% --- Find the maximum of the correlation function after interpolation
302function [vector,FF] = SUBPIX2DGAUSS (result_conv,x,y)
303%------------------------------------------------------------------------
304% vector=[0 0]; %default
305FF=true;
306peaky=y;
307peakx=x;
308[npy,npx]=size(result_conv);
309if (x < npx) && (y < npy) && (x > 1) && (y > 1)
310    FF=false;
311max_conv=result_conv(y,x);% max correlation
312    peak2noise= max(4,max_conv/std(reshape(result_conv,1,[])));% ratio of max conv to standard deviation of correlations (estiamtion of noise level), set to value 4 if it is too low
313    result_conv=result_conv*peak2noise/max_conv;% renormalise the correlation with respect to the noise
314    result_conv(result_conv<1)=1; %set to 1 correlation values smaller than 1  (=0 by discretisation, to avoid divergence in the log)
315    for i=-1:1
316        for j=-1:1
317  %following 15 lines based on  H. Nobach and M. Honkanen (2005)
318  % Two-dimensional Gaussian regression for sub-pixel displacement
319  % estimation in particle image velocimetry or particle position
320  % estimation in particle tracking velocimetry
321  % Experiments in Fluids (2005) 38: 511-515
322            c10(j+2,i+2)=i*log(result_conv(y+j, x+i));
323            c01(j+2,i+2)=j*log(result_conv(y+j, x+i));
324            c11(j+2,i+2)=i*j*log(result_conv(y+j, x+i));
325            c20(j+2,i+2)=(3*i^2-2)*log(result_conv(y+j, x+i));
326            c02(j+2,i+2)=(3*j^2-2)*log(result_conv(y+j, x+i));
327        end
328    end
329    c10=(1/6)*sum(sum(c10));
330    c01=(1/6)*sum(sum(c01));
331    c11=(1/4)*sum(sum(c11));
332    c20=(1/6)*sum(sum(c20));
333    c02=(1/6)*sum(sum(c02));
334    deltax=(c11*c01-2*c10*c02)/(4*c20*c02-c11*c11);
335    deltay=(c11*c10-2*c01*c20)/(4*c20*c02-c11*c11);
336    if abs(deltax)<1
337        peakx=x+deltax;
338    end
339    if abs(deltay)<1
340        peaky=y+deltay;
341    end
342end
343vector=[peakx-floor(npx/2)-1 peaky-floor(npy/2)-1];
344
345%------------------------------------------------------------------------
346% --- Find the maximum of the correlation function after quadratic interpolation
347function [vector,F] = quadr_fit(result_conv,x,y)
348[npy,npx]=size(result_conv);
349if x<4 || y<4 || npx-x<4 ||npy-y <4
350    F=1;
351    vector=[x y];
352else
353    F=0;
354    x_ind=x-4:x+4;
355    y_ind=y-4:y+4;
356    x_vec=0.25*(x_ind-x);
357    y_vec=0.25*(y_ind-y);
358    [X,Y]=meshgrid(x_vec,y_vec);
359    coord=[reshape(X,[],1) reshape(Y,[],1)];
360    result_conv=reshape(result_conv(y_ind,x_ind),[],1);
361   
362   
363    % n=numel(X);
364    % x=[X Y];
365    % X=X-0.5;
366    % Y=Y+0.5;
367    % y = (X.*X+2*Y.*Y+X.*Y+6) + 0.1*rand(n,1);
368    p = polyfitn(coord,result_conv,2);
369    A(1,1)=2*p.Coefficients(1);
370    A(1,2)=p.Coefficients(2);
371    A(2,1)=p.Coefficients(2);
372    A(2,2)=2*p.Coefficients(4);
373    vector=[x y]'-A\[p.Coefficients(3) p.Coefficients(5)]';
374    vector=vector'-[floor(npx/2) floor(npy/2)]-1 ;
375    % zg = polyvaln(p,coord);
376    % figure
377    % surf(x_vec,y_vec,reshape(zg,9,9))
378    % hold on
379    % plot3(X,Y,reshape(result_conv,9,9),'o')
380    % hold off
381end
382
383
Note: See TracBrowser for help on using the repository browser.