4

I am working with normxcorr2 function in Matlab for template matching. However, what I want to do is different from what normxcorr2 does. The built-in normxcorr2 computes cross-correlation taking into account all the pixels in a rectangular template. But I only want certain pixels to participate in the normalized cross correlation process.

For example, I want only the ring-like white region in the following image to be used as a template while correlating. (the black region inside should not be used for computation)

enter image description here

And thus if I correlate the above template with the following image, I am likely to get a normalized value of around 1.0 (considering the outer circles are of same diameter in both images)

enter image description here

I have checked out this solution:- matlab template matching only for 0 (or 1) in matrix but it is not generic.

Can anybody help me with a more general solution in matlab? This can be used to track objects by their outer shapes

EDIT:- This is the entire image for those who want to have a look at it. I want to detect the ball only by the outer circular rim, not the inner details.

enter image description here

Community
  • 1
  • 1
  • Please post the original image as well. – kkuilla Jun 12 '14 at 14:19
  • @soumyadip93 : I am the original author of the solution to that post that you have linked to. Why is it not generic? It should work for any kind of template you choose. – rayryeng Jun 12 '14 at 14:35
  • @rayryeng : Maybe I did not understand your code totally but you have used a certain area for searching within the image. Why did you not use the whole image? – soumyadip.ghosh Jun 12 '14 at 14:44
  • @soumyadip93 : The OP of that question only wanted to use pixels in the correlation calculation that were within the black areas of the template only. I also searched **the entire image**. Your situation is the same, except that you are searching for pixels that are white within your template. As such, I believe your situation and the other question is the same.... is it not? All you would have to do in that case is change a couple of lines. One is where you skip if all pixels in the template **are 0**, and the next is when you skip inverting the template. – rayryeng Jun 12 '14 at 14:47
  • @soumyadip93 You would also have to modify the line at the very top where I am looking for all pixels within the template that are zero. Because you have the opposite case, you can safely remove this line as the template pixels are 1. – rayryeng Jun 12 '14 at 14:51
  • @rayryeng .. Yes, I agree to the fact that the two posts are quite similiar. but you also mentioned in your post that it required more computation time. I need this tracking for a real-time solution. Actually I think that the image patch that you are grabbing from the whole image can multiplied by a suitable mask(the circular rim in my case) before cross-correlation with the template image. – soumyadip.ghosh Jun 12 '14 at 14:57
  • @soumyadip93 : You didn't specify real-time! In that case, my solution won't work. Also, I don't think `normxcorr2` is real-time as well. That post was mainly for a MATLAB beginner, but if you want something more real-time, I can't see any way using the Image Processing Toolbox, which is why the OP of that post was going to use MEX. – rayryeng Jun 12 '14 at 15:05
  • @soumyadip93: I think we can achieve the same thing by using `colfilt` and `im2col`. Would you like me to write a solution for you? – rayryeng Jun 12 '14 at 17:14
  • @rayryeng..Please write a solution, I would be grateful..also try to keep computation time as less as possible – soumyadip.ghosh Jun 12 '14 at 17:18
  • @soumyadip93 : Done. Take a look. This is untested, but with the small sample sizes that I was specifying, this should do what is asked. Good luck. – rayryeng Jun 12 '14 at 19:55
  • Have you tried the Hough circle transform? – japreiss Jun 13 '14 at 14:13
  • @japreiss..No, I have not. Is it computationally expensive? – soumyadip.ghosh Jun 13 '14 at 16:18
  • @soumyadip93 - Do you require any further assistance? – rayryeng Jun 16 '14 at 05:47
  • @rayryeng..I appreciate both the solutions to my question. As of now, I am fine. If I require further assistance, I will ask. Please do not close the question btw. – soumyadip.ghosh Jun 16 '14 at 07:10

2 Answers2

3

This is a derivative of a previous post where I provided an answer to here: matlab template matching only for 0 (or 1) in matrix

However, this solution used for loops which is quite inefficient. As such, we can use im2col, bsxfun and col2im to help us perform this more quickly. im2col takes overlapping regions in your image and places them each into individual columns. Essentially, this takes sliding windows of your image like you would with any kind of spatial image filtering and collects all pixels within a sliding window and places each window as individual columns.

Supposing the size of your template is M x N, and the size of your image you want to search in is R x C, and supposing that your image template is called imTemplate while the image you want to search in is imSearch, we can do the following setup. Let's also assume that both images are binary.

[M, N] = size(imTemplate);
[R, C] = size(imSearch);

%// Cast to double for precision
imTemplate = im2double(imTemplate);
imSearch = im2double(imSearch);

neigh = im2col(imSearch, [M, N]);
templateCol = imTemplate(:); %// Ensures we place template into single column

Now, you wish to exclude all pixels that are inside the circular boundary. As such, what we can do is invert the image so that black pixels become white, then remove all of the pixels around the border. This should then give us the interior of the circle.

imInvert = ~imTemplate;
imInvertNoBorder = imclearborder(imInvert, 8); %// Search 8-pixel neighbourhood

We will use this to figure out what pixels we are going to remove from searching. This can be done by:

rowsToRemove = imInvertNoBorder(:) == 1;

Now, what we can do is finally remove those pixels that are within the interior of the circle to not be searched in our correlation scheme.

neigh(rowsToRemove,:) = [];

What we can do now is compute the NCC over all of these columns. If you recall, the NCC between two signals is the following:

NCC
(source: www.jot.fm)

As such, we need to subtract the mean from each neighbourhood, and we also need to subtract the mean from each of the columns. We then compute the formula as shown above. We can easily achieve this vectorized in MATLAB like so:

neighMeanSubtract = bsxfun(@minus, neigh, mean(neigh));
templateMeanSubtract = templateCol - mean(templateCol);

We can compute the numerator of the NCC for each neighbourhood (before we sum) as follows:

numerator = bsxfun(@times, neighMeanSubtract, templateMeanSubtract);

Now, all we have to do is sum all of the columns and that will give us our final numerator:

sumNumerator = sum(numerator);

The denominator can be computed like so:

denominator1 = sqrt(sum(neighMeanSubtract.*neighMeanSubtract));
denominator2 = sqrt(sum(templateMeanSubtract.*templateMeanSubtract));
sumDenominator = denominator1 .* denominator2;

Finally, our NCC can be computed as so:

NCC = sumNumerator ./ sumDenominator;

You'll notice that this is a single row of values. Each row corresponds to the output defined at a neighbourhood. As such, we also need to reshape this back into a matrix, and so you can use col2im:

finalOutput = col2im(NCC, [M, N], [R, C]);

The above statement will take overlapping neighbourhoods of M x N defined in NCC, and reshapes it so that it becomes a R x C matrix. Sometimes, you will get a divide by zero error, especially if the neighbourhood search window is all uniform. As such, you will get NaN numbers. Areas of no variation are assumed to have no correlation in image processing, and so let's zero these locations:

finalOutput(isnan(finalOutput)) = 0;

If you want to find the location of where the highest correlation is, simply do:

[rowNCC, colNCC] = find(finalOutput == max(finalOutput(:)));

If you want to interpret negative correlation, that completely depends on your application. If you want to ensure that your template matching algorithm is rotationally invariant, then you should actually check for the max of the absolute values. Negative correlation simply means that the match between the template and a neighbourhood is simply rotated. As such, a better way to find the best neighbourhood is:

maxCoeff = max(abs(finalOutput(:)));
[rowNCC, colNCC] = find(abs(finalOutput) == maxCoeff);

For your copying and pasting pleasure, here is the code in its entirety:

function [rowNCC, colNCC] = testCorr(imTemplate, imSearch)
    [M, N] = size(imTemplate);
    [R, C] = size(imSearch);

    %// Cast to double for precision
    imTemplate = im2double(imTemplate);
    imSearch = im2double(imSearch);

    neigh = im2col(imSearch, [M, N]);
    templateCol = imTemplate(:); %// Ensures we place template into single column

    imInvert = ~imTemplate;
    imInvertNoBorder = imclearborder(imInvert, 8); %// Search 8-pixel neighbourhood
    rowsToRemove = imInvertNoBorder(:) == 1;
    neigh(rowsToRemove,:) = [];

    neighMeanSubtract = bsxfun(@minus, neigh, mean(neigh));
    templateMeanSubtract = templateCol - mean(templateCol);

    numerator = bsxfun(@times, neighMeanSubtract, templateMeanSubtract);
    sumNumerator = sum(numerator);

    denominator1 = sqrt(sum(neighMeanSubtract.*neighMeanSubtract));
    denominator2 = sqrt(sum(templateMeanSubtract.*templateMeanSubtract));
    sumDenominator = denominator1 .* denominator2;

    NCC = sumNumerator ./ sumDenominator;    

    finalOutput = col2im(NCC, [M, N], [R, C]);
    finalOutput(isnan(finalOutput)) = 0;

    maxCoeff = max(abs(finalOutput(:)));
    [rowNCC, colNCC] = find(abs(finalOutput) == maxCoeff);
end

Good luck!

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
rayryeng
  • 102,964
  • 22
  • 184
  • 193
  • 1
    This post is dedicated to Luis Mendo and Divakar for being `bsxfun` masters :) – rayryeng Jun 12 '14 at 20:01
  • Can NCC get single similarity value instead of row or matrix? I need to NCC calc similarity similar to [mutual information (MI)](https://stackoverflow.com/a/23691992/3477733). hint: for gray images. – Mohammad nagdawi Aug 11 '18 at 06:55
  • @Mohammadnagdawi I'm sorry, I have no idea what you're asking. – rayryeng Aug 11 '18 at 16:22
  • I am getting an error when using im2col in this function. It says array exceeds max array size preference. My imSearch is 644*3456 and imTemplate is 321*2566 – Afzal Feb 28 '20 at 15:05
  • @Afzal That image and template will exhaust your memory. Having that many neighbourhoods for that large image and template will not work with this code. – rayryeng Feb 28 '20 at 16:05
  • @rayryeng What would you recommend in this case? – Afzal Feb 29 '20 at 17:50
  • Please see the images at https://uk.mathworks.com/matlabcentral/answers/508140-find-template-in-image – Afzal Feb 29 '20 at 18:54
1

Ok, let's give it a try... This solution tries to use existing normxcorr2 implementation and modify it to solve yoru problem.

The formula for normalized cross correlation is:

enter image description here

In this case you want to change the integration boundaries for every window. This is turn affects both standard deviations and the correlation itself. Lets tackle it in several steps:

Step #1: Get the correlation right

We can do this by modifying the template image:

template_fix = template;
mean_template_mask = mean(template(mask == 1));
template_fix(mask == 0) = mean_template_mask;
result = normxcorr2(template_fix, query)

Notice that by making this change we make the mean value of the template to be equal to the mean value of the template in side the mask. this way all template pixels outside the mask don't contribute to the integration as they are equal to the mean value.

Step #2: Fix template std

size_mask = sum(mask(:));
size_template = numel(template);
std_template = std2(template);
std_template_masked = sqrt(sum((template_fix(:) - mean_template_mask).^2)/size_mask);
result = result * (std_template/std_template_masked);

Step #3: Fix query std

sum_filt = ones(size(template));
std_query = filter2(query.^2, sum_filt) - filter2(query, sum_filt).^2/size_template;
std_query = sqrt(std_query/size_template);

std_query_mask = filter2(query.^2, mask) - filter2(query, mask).^2/size_mask;    
std_query_mask = sqrt(std_query_mask/size_mask);

result = result .* std_query ./ std_query_mask;

My Matlab is not responding so I didn't have the chance to test it in practice. Unless I missed some errors it should be mathematically equivalent.

This solution does some extra convolutions but it doesn't process overlapping pixels more than once.

If you use the same template image multiple times then you could refactor steps 1 & 2 to run only once for preprocessing. Although both shouldn't be computationally expensive.

Different approach: Straight forward

Here is a different, straightforward approach that doesn't use the original normxcorr2 function. This code can be easily optimized for memory usage in expense of readability.

enter image description here

enter image description here

% q for query, t for template and mask for mask
% shape = 'full' or 'same' or 'valid'

t_mask = t .* mask;
n      = numel(mask);
tq_sum = filter2(t_mask,q, shape);

q_sum  = filter2(mask, q, shape);    
q_mean = q_sum/n;
t_sum  = sum(t_mask(:));
t_mean = t_sum/n;

res1 = tq_sum - t_mean*q_sum - t_sum*q_mean + t_mean*q_mean*n;

t_std = sqrt((sum(t_mask(:).^2) - sum(t_mask(:)).^2/n)/n);
q_std = sqrt((filter2(mask, q.^2, shape) - q_sum.^2/n)/n);

res = res1 ./ (n * t_std * q_std)
Xyand
  • 4,470
  • 4
  • 36
  • 63
  • I like the logic of this solution. I am testing it now. Can you please explain why it does some extra convolutions? – soumyadip.ghosh Jun 13 '14 at 07:53
  • It doesn't do extra convolutions intentionally. But it uses `filter2` to calculate the original std and the modified one. I call it extra because it was already calculated somewhere inside `normxcorr21` and we're actually re-evaluating it. – Xyand Jun 13 '14 at 11:36
  • It is giving many errors. First of all, for 2-d matrix, std2 has to be used, not std. Also when you are calculating the standard deviation of the query matrix, it is showing mismatch in matrix dimensions. Don't you think in the calculation of std_query, "sum_query" must be replaced by "sum_query^2" ? – soumyadip.ghosh Jun 13 '14 at 12:47
  • You are correct, I wrote it late at night :) . I made some changes, let me know if it works. – Xyand Jun 13 '14 at 14:06
  • Still there is mismatch of dimensions in the last line. It is because the dimensions of query are different from std_query(and std_query_mask). Also the 2nd line should be '=' instead of '==' – soumyadip.ghosh Jun 13 '14 at 17:28
  • As I said I have no working Matlab. The size difference must be a result of the difference in the default implementations of `filter2` and `normxcorr`. Try adding `'full'` to the `filter2` calls. If you managed to solve it differently feel free to update the answer. A relevant question: http://stackoverflow.com/questions/9145107/an-elegant-way-to-get-the-output-of-normxcorr2-in-a-manner-similar-to-conv2 – Xyand Jun 13 '14 at 17:43
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/55592/discussion-between-xyand-and-soumyadip93). – Xyand Jun 13 '14 at 17:49