How to use distance to extract features and compare images: : matlab - matlab

I was trying to code for feature extraction from the two images, which are actually similar. I tried to extract the intersection points from both of the image and calculated the distance from one intersection point to all other points. This procedure was iterated for all points and in both images.
Then I compared the distance between points in both images But I found that even for dissimilar images am getting same kind of distance and am not able to distinguish them.
Is there any way in this method which will improve the code or is there any other way to find the similarity.
I = bwmorph(I,'skel',Inf);
II = bwmorph(II,'skel',Inf);
[i,j] = ind2sub(size(I),find(bwmorph(bwmorph(I,'thin',Inf),'branchpoint') == 1));
[i1,j1] = ind2sub(size(II),find(bwmorph(bwmorph(II,'thin',Inf),'branchpoint') == 1));
figure,imshow(I); hold on; plot(j,i,'rx');
figure,imshow(II); hold on; plot(j1,i1,'rx')
m=size(i,1);
n=size(j,1);
m1=size(i1,1);
n1=size(j1,1);
for x=1:m
for y=1:n
d1(y,x)=round(sqrt((i(y,1)-i(x,1)).^2+(j(y,1)-j(x,1)).^2));
end
end
for x1=1:m1
for y1=1:n1
dd1(y1,x1)=round(sqrt((i1(y1,1)-i1(x1,1)).^2+(j1(y1,1)-j1(x1,1)).^2));
end
end
size(d1);
k1=reshape(d1,1,m*n);
k=sort(k1);
k=unique(k);
size(dd1);
k2=reshape(dd1,1,m1*n1);
k2=sort(k2);
k2=unique(k2);
z = intersect(k,k2)
length(z);
if length(z)>20
disp('similar images');
else
disp('dissimilar images');
end
This is a part of my code where I tried to extract features.
input1
input2
skel 1
skel2

I think your code is not the problem. Instead, it seems that either your feature descriptor is not powerful enough or your comparison method is not powerful enough, or a combination of the two. This gives us several options for how to explore solutions to the problem.
Feature Descriptor
You are constructing an image feature consisting of the distances between skeleton intersection points. This is an unusual approach and a very interesting one. It reminds me of peak constellations, a feature used by Shazam to audio-fingerprint songs. If you are interested in exploring, that more sophisticated technique, take a look at "An Industrial Strength Audio Search Algorithm" by Avery Li-Chun Wang. I believe you could adapt their feature descriptor to your application.
However, if you want a simpler solution there are some other options as well. Your current descriptor uses unique to find a set of unique distances between the skeleton intersection points. Take a look at the following images of a line and an equilateral triangle both with 5 unit line lengths. If we use the unique distances between vertices to make the feature, the two images have identical features, but we can also count the number of lines of each length in a histogram.
The histogram preserves more of the image structure as part of the feature. Using a histogram might help distinguish better between your similar and dissimilar cases.
Here's some demo code for histogram features using the Matlab demo images pears.png and peppers.png. I had difficulty extracting the skeleton from your provided images, but you should be able to adapt this code easily to your application.
I1 = = im2bw(imread('peppers.png'));
I2 = = im2bw(imread('pears.png'));
I1_skel = bwmorph(I1,'skel',Inf);
I2_skel = bwmorph(I2,'skel',Inf);
[i1,j1] = ind2sub(size(I1_skel),find(bwmorph(bwmorph(I1_skel,'thin',Inf),'branchpoint') == 1));
[i2,j2] = ind2sub(size(I2_skel),find(bwmorph(bwmorph(I2_skel,'thin',Inf),'branchpoint') == 1));
%You used a for loop to find the distance between each pair of
%intersections. There is a function for this.
d1 = round(pdist2([i1, j1], [i1, j1]));
d2 = round(pdist2([i2, j2], [i2, j2]));
%Choose a number of bins for the histogram.
%This will be the length of the feature.
%More bins will preserve more structure.
%Fewer bins will help generalize between similar but not identical images.
num_bins = 100;
%Instead of using `unique` to remove repetitions use `histcounts` in R2014b
%feature1 = histcounts(d1(:), num_bins);
%feature2 = histcounts(d2(:), num_bins);
%Use `hist` for pre R2014b Matlab versions
feature1 = hist(d1(:), num_bins);
feature2 = hist(d2(:), num_bins);
%Normalize the features
feature1 = feature1 ./ norm(feature1);
feature2 = feature2 ./ norm(feature2);
figure; bar([feature1; feature2]');
title('Features'); legend({'Feature 1', 'Feature 2'});
xlim([0, num_bins]);
Here are what the detected intersection points are in each image
Here are the resulting features. You can see the clear differences between images.
Feature Comparison
The second part to consider is how you compare your features. Currently, you are simply looking for >20 similar distances. With the 'peppers.png' and 'pears.png' test images distributed with Matlab, I find more than 2000 intersection points in one image and 260 in the other. With so many points, it is trivial to have an overlap of >20 similar distances. In your images, the number of intersection points is much smaller. You could carefully adjust the threshold of similar distances, but I think this metric is probably to simplistic.
In Machine Learning, a simple way to compare two feature vectors is vector similarity or distance. There are multiple distance metrics you could explore. Common ones include
Cosine Distance
score_cosine = feature1 * feature2'; %Cosine distance between vectors
%Set a threshold for cosine similarity [0, 1] where 1 is identical and 0 is perpendicular
cosine_threshold = .9;
disp('Cosine Compare')
disp(score_cosine)
if score_cosine > cosine_threshold
disp('similar images');
else
disp('dissimilar images');
end
Euclidean Distance
score_euclidean = pdist2(feature1, feature2);
%Set a threshold for euclidean similarity where smaller is more similar
euclidean_threshold = 0.1;
disp('Euclidean Compare')
disp(score_euclidean)
if score_euclidean < euclidean_threshold
disp('similar images');
else
disp('dissimilar images');
end
If these don't work, you may need to train a classifier to find a more complicated function to distinguish between similar and dissimilar images.

Related

Looking for a tool that extracts data from a plot figure ( here 2D contours from Covariance matrix or Markov chains) and reproduce the original figure

I am looking for an application or a tool which is able for example to extract data from a 2D contour plot like below :
I have seen https://dash-gallery.plotly.host/Portal/ tool or https://plotly.com/dash/ , https://automeris.io/ , but I have test them and this is difficult to extract data (here actually, the data are covariance matrices with ellipses, but I would like to extend it if possible to Markov chains).
If someone could know if there are more efficient tools, mostly from this kind of 2D plot.
I am also opened to commercial applications. I am on MacOS 11.3.
If I am not on the right forum, please let me know it.
UPDATE 1:
I tried to apply the method in Matlab with the script below from this previous post :
%// Import the data:
imdata = importdata('Omega_L_Omega_m.png');
Gray = rgb2gray(imdata.cdata);
colorLim = [-1 1]; %// this should be set manually
%// Get the area of the data:
f = figure('Position',get(0,'ScreenSize'));
imshow(imdata.cdata,'Parent',axes('Parent',f),'InitialMagnification','fit');
%// Get the area of the data:
title('Click with the cross on the most top left area of the *data*')
da_tp_lft = round(getPosition(impoint));
title('Click with the cross on the most bottom right area of the *data*')
da_btm_rgt = round(getPosition(impoint));
dat_area = double(Gray(da_tp_lft(2):da_btm_rgt(2),da_tp_lft(1):da_btm_rgt(1)));
%// Get the area of the colorbar:
title('Click with the cross within the upper most color of the *colorbar*')
ca_tp_lft = round(getPosition(impoint));
title('Click with the cross within the bottom most color of the *colorbar*')
ca_btm_rgt = round(getPosition(impoint));
cmap_area = double(Gray(ca_tp_lft(2):ca_btm_rgt(2),ca_tp_lft(1):ca_btm_rgt(1)));
close(f)
%// Convert the colormap to data:
data = dat_area./max(cmap_area(:)).*range(colorLim)-abs(min(colorLim));
It seems that I get data in data array but I don't know how to exploit it to reproduce the original figure from these data.
Could anyone see how to plot with Matlab this kind of plot with the data I have normally extracted (not sure the Matlab. script has generated all the data for green, orange and blue contours, with each confidence level, that is to say, 68%, 95%, 99.7%) ?
UPDATE 2: I have had first elements of answer on the following link :
partial answer but not fully completed
I cite elements of the approach :
clc
clear all;
imdata = imread('https://www.mathworks.com/matlabcentral/answers/uploaded_files/642495/image.png');
close all;
Gray = rgb2gray(imdata);
yax=sum(conv2(single(Gray),[-1 -1 -1;0 0 0; 1 1 1],'valid'),2);
xax=sum(conv2(single(Gray),[-1 -1 -1;0 0 0; 1 1 1]','valid'),1);
figure(1),subplot(211),plot(xax),subplot(212),plot(yax)
ROIy = find(abs(yax)>1e5);
ROIyinner = find(diff(ROIy)>5);
ROIybounds = ROIy([ROIyinner ROIyinner+1]);
ROIx = find(abs(xax)>1e5);
ROIxinner = find(diff(ROIx)>5);
ROIxbounds = ROIx([ROIxinner ROIxinner+1]);
PLTregion = Gray(ROIybounds(1):ROIybounds(2),ROIxbounds(1):ROIxbounds(2));
PLTregion(PLTregion==255)=nan;
figure(2),imagesc(PLTregion)
[N X]=hist(single(PLTregion(:)),0:255);
figure(3),plot(X,N),set(gca,'yscale','log')
PLTitems = find(N>2000)% %limit "color" of interest to items with >1000 pixels
PLTitems = 1×10
1 67 90 101 129 132 144 167 180 194
PLTvalues = X(PLTitems);
PLTvalues(1)=[]; %ignore black?
%test out region 1
for ind = 1:numel(PLTvalues)
temp = zeros(size(PLTregion));
temp(PLTregion==PLTvalues(ind) | (PLTregion<=50 & PLTregion>10))=255;
% figure(100), imagesc(temp)
temp = bwareaopen(temp,1000);
temp = imfill(temp,'holes');
figure(100), subplot(3,3,ind),imagesc(temp)
figure(101), subplot(3,3,ind),imagesc(single(PLTregion).*temp,[0 255])
end
If someone could know how to improve these first interesting results, this would be fine to mention it.
Restating the problem - My understanding given the different comments and your updates is the following:
someone other than you is in possession of data, which as it happens is 2D data, i.e. an Nx2 matrix;
using the covariance matrix, they are effectively saying something about the joint distribution of these two dimensions, specifically about the variance;
if they assume a Gaussian distribution, as is implied by your comment regarding 68%, 95% and 99.7% for 1sigma, 2sigma and 3sigma, they can draw ellipses which represent the 2D-normal distribution: these are in fact some of the contour lines associated with the 3D "bell" surface;
you have obtained the contour lines in a graph and are trying to obtain the covariance matrix (not the original data...);
you are concerned about the complexity of having to extract the information from each ellipsis.
Partial answer:
It is impossible to recover the original data, I hope you are already aware of that, but in case you are not let's just note that the covariance matrix is a summary statistic of the data, much like the average, and although it says something about the data many different datasets could happen to have the same summary statistic (the same way many different sets of numbers can give you an average of 10).
It is possible to somewhat recover the covariance matrix, i.e. the 3 numbers a, b and c in the matrix [a,b;b,c], though the error in doing so will likely be large because of how imprecise the pixel representation is. Essentially, you will be looking for the dimensions of the two axes, for the variances, as well as the angle of one of the axes, for the covariance.
Unless I am mistaken, under the Gaussian assumption above, you only need to measure this for one of the three ellipses, and then factor by whatever number of sigmas that contour represents. Here you might want to either use the best-defined ellipse, or attempt to use the largest one, which will provide the maximum precision for your measurements (cf. pixelization).
Also, the problem of finding the axes and angle for the ellipse need not be as complex as what it seems like in your first trials: instead of trying to find the contour of the ellipses, find the bounding rectangle.
In order to further simplify this process, if your images are color-coded the way you show, then a filter on blue pixels might be enough in terms of image processing. Then simply take the minimum and maximum (x,y) coordinates in order to obtain the bounding rectangle.
Once the bounding rectangle is obtained, find the equation to your ellipse (that's a question for a math group, but you could start here for example).
Happy filtering!

Finding shortest length between to regions of interest

I have a binary image with several regions of interest which I have identified via bwconncomp. I am trying to find the shortest point connecting each of these regions. I was considering using dilation with larger and larger kernel sizes within a loop with code similar to below, pausing the loop when the number of connected components drops, then maybe identifying those which have connected by sizable changes in centroids and using the number of iterations times two to gives the approximate distance? I feel like there should be a better way of doing this?
distancebetweenROIS=[];
M11=tempBimage;
for c=1:50
TT=bwconncomp(M11);
seDil=strel('disk',c);
M11=imdilate(tempBimage,seDil);
YY=bwconncomp(M11);
if length(TT.PixelIdxList)>length(YY.PixelIdxList)
distancebetweenROIS(end+1)=c*2;
end
end
Using bwdist and bwlabel, you can find the shortest distance of any feature to all other features. All you have to do is then to loop through the features.
%// labeledImage is 1 on feature #1, 2 on feature #2, etc
labeledImage = bwlabel(yourBinaryImage);
nLabels = max(labeledImage(:));
%// find the distance between each label and all other labels
distMat = zeros(nLabels, nLabels);
for iLabel = 1:nLabels
%// distance transform - every pixel is the distance to the nearest
%// non-zero pixel, i.e. the location of label iLabel
dist = bwdist(labeledImage==iLabel);
%// use accumarray b/c we can
%// get rid of the zeros in labeledImage, though, as there is no index 0
distMat(:,iLabel) = accumarray(dist(labeledImage>0),labeledImage(labeledImage>0),[],#min);
end
Note that the distance is equivalent to "how many hops do I need at minimum if I start on feature X and hop from pixel to pixel onto feature Y". If you need the distance between centroids, regionprops(yourBinaryImage,'Centroids') followd by pdist2 is the better approach.

Clustering an image using Gaussian mixture models

I want to use GMM(Gaussian mixture models for clustering a binary image and also want to plot the cluster centroids on the binary image itself.
I am using this as my reference:
http://in.mathworks.com/help/stats/gaussian-mixture-models.html
This is my initial code
I=im2double(imread('sil10001.pbm'));
K = I(:);
mu=mean(K);
sigma=std(K);
P=normpdf(K, mu, sigma);
Z = norminv(P,mu,sigma);
X = mvnrnd(mu,sigma,1110);
X=reshape(X,111,10);
scatter(X(:,1),X(:,2),10,'ko');
options = statset('Display','final');
gm = fitgmdist(X,2,'Options',options);
idx = cluster(gm,X);
cluster1 = (idx == 1);
cluster2 = (idx == 2);
scatter(X(cluster1,1),X(cluster1,2),10,'r+');
hold on
scatter(X(cluster2,1),X(cluster2,2),10,'bo');
hold off
legend('Cluster 1','Cluster 2','Location','NW')
P = posterior(gm,X);
scatter(X(cluster1,1),X(cluster1,2),10,P(cluster1,1),'+')
hold on
scatter(X(cluster2,1),X(cluster2,2),10,P(cluster2,1),'o')
hold off
legend('Cluster 1','Cluster 2','Location','NW')
clrmap = jet(80); colormap(clrmap(9:72,:))
ylabel(colorbar,'Component 1 Posterior Probability')
But the problem is that I am unable to plot the cluster centroids received from GMM in the primary binary image.How do i do this?
**Now suppose i have 10 such images in a sequence And i want to store the information of their mean position in two cell array then how do i do that.This is my code foe my new question **
images=load('gait2go.mat');%load the matrix file
for i=1:10
I{i}=images.result{i};
I{i}=im2double(I{i});
%determine 'white' pixels, size of image can be [M N], [M N 3] or [M N 4]
Idims=size(I{i});
whites=true(Idims(1),Idims(2));
df=I{i};
%we add up the various color channels
for colori=1:size(df,3)
whites=whites & df(:,:,colori)>0.5;
end
%choose indices of 'white' pixels as coordinates of data
[datax datay]=find(whites);
%cluster data into 10 clumps
K = 10; % number of mixtures/clusters
cInd = kmeans([datax datay], K, 'EmptyAction','singleton',...
'maxiter',1000,'start','cluster');
%get clusterwise means
meanx=zeros(K,1);
meany=zeros(K,1);
for i=1:K
meanx(i)=mean(datax(cInd==i));
meany(i)=mean(datay(cInd==i));
end
xc{i}=meanx(i);%cell array contaning the position of the mean for the 10
images
xb{i}=meany(i);
figure;
gscatter(datay,-datax,cInd); %funky coordinates for plotting according to
image
axis equal;
hold on;
scatter(meany,-meanx,20,'+'); %same funky coordinates
end
I am able to get 10 images segmented but no the values of themean stored in the cell arrays xc and xb.They r only storing [] in place of the values of means
I decided to post an answer to your question (where your question was determined by a maximum-likelihood guess:P), but I wrote an extensive introduction. Please read carefully, as I think you have difficulties understanding the methods you want to use, and you have difficulties understanding why others can't help you with your usual approach of asking questions. There are several problems with your question, both code-related and conceptual. Let's start with the latter.
The problem with the problem
You say that you want to cluster your image with Gaussian mixture modelling. While I'm generally not familiar with clustering, after a look through your reference and the wonderful SO answer you cited elsewhere (and a quick 101 from #rayryeng) I think you are on the wrong track altogether.
Gaussian mixture modelling, as its name suggests, models your data set with a mixture of Gaussian (i.e. normal) distributions. The reason for the popularity of this method is that when you do measurements of all sorts of quantities, in many cases you will find that your data is mostly distributed like a normal distribution (which is actually the reason why it's called normal). The reason behind this is the central limit theorem, which implies that the sum of reasonably independent random variables tends to be normal in many cases.
Now, clustering, on the other hand, simply means separating your data set into disjoint smaller bunches based on some criteria. The main criterion is usually (some kind of) distance, so you want to find "close lumps of data" in your larger data set. You usually need to cluster your data before performing a GMM, because it's already hard enough to find the Gaussians underlying your data without having to guess the clusters too. I'm not familiar enough with the procedures involved to tell how well GMM algorithms can work if you just let them work on your raw data (but I expect that many implementations start with a clustering step anyway).
To get closer to your question: I guess you want to do some kind of image recognition. Looking at the picture, you want to get more strongly correlated lumps. This is clustering. If you look at a picture of a zoo, you'll see, say, an elephant and a snake. Both have their distinct shapes, and they are well separated from one another. If you cluster your image (and the snake is not riding the elephant, neither did it eat it), you'll find two lumps: one lump elephant-shaped, and one lump snake-shaped. Now, it wouldn't make sense to use GMM on these data sets: elephants, and especially snakes, are not shaped like multivariate Gaussian distributions. But you don't need this in the first place, if you just want to know where the distinct animals are located in your picture.
Still staying with the example, you should make sure that you cluster your data into an appropriate number of subsets. If you try to cluster your zoo picture into 3 clusters, you might get a second, spurious snake: the nose of the elephant. With an increasing number of clusters your partitioning might make less and less sense.
Your approach
Your code doesn't give you anything reasonable, and there's a very good reason for that: it doesn't make sense from the start. Look at the beginning:
I=im2double(imread('sil10001.pbm'));
K = I(:);
mu=mean(K);
sigma=std(K);
X = mvnrnd(mu,sigma,1110);
X=reshape(X,111,10);
You read your binary image, convert it to double, then stretch it out into a vector and compute the mean and deviation of that vector. You basically smear your intire image into 2 values: an average intensity and a deviation. And THEN you generate 111*10 standard normal points with these parameters, and try to do GMM on the first two sets of 111. Which are both independently normal with the same parameter. So you probably get two overlapping Gaussians around the same mean with the same deviation.
I think the examples you found online confused you. When you do GMM, you already have your data, so no pseudo-normal numbers should be involved. But when people post examples, they also try to provide reproducible inputs (well, some of them do, nudge nudge wink wink). A simple method for this is to generate a union of simple Gaussians, which can then be fed into GMM.
So, my point is, that you don't have to generate random numbers, but have to use the image data itself as input to your procedure. And you probably just want to cluster your image, instead of actually using GMM to draw potatoes over your cluster, since you want to cluster body parts in an image about a human. Most body parts are not shaped like multivariate Gaussians (with a few distinct exceptions for men and women).
What I think you should do
If you really want to cluster your image, like in the figure you added to your question, then you should use a method like k-means. But then again, you already have a program that does that, don't you? So I don't really think I can answer the question saying "How can I cluster my image with GMM?". Instead, here's an answer to "How can I cluster my image?" with k-means, but at least there will be a piece of code here.
%set infile to what your image file will be
infile='sil10001.pbm';
%read file
I=im2double(imread(infile));
%determine 'white' pixels, size of image can be [M N], [M N 3] or [M N 4]
Idims=size(I);
whites=true(Idims(1),Idims(2));
%we add up the various color channels
for colori=1:Idims(3)
whites=whites & I(:,:,colori)>0.5;
end
%choose indices of 'white' pixels as coordinates of data
[datax datay]=find(whites);
%cluster data into 10 clumps
K = 10; % number of mixtures/clusters
cInd = kmeans([datax datay], K, 'EmptyAction','singleton',...
'maxiter',1000,'start','cluster');
%get clusterwise means
meanx=zeros(K,1);
meany=zeros(K,1);
for i=1:K
meanx(i)=mean(datax(cInd==i));
meany(i)=mean(datay(cInd==i));
end
figure;
gscatter(datay,-datax,cInd); %funky coordinates for plotting according to image
axis equal;
hold on;
scatter(meany,-meanx,20,'ko'); %same funky coordinates
Here's what this does. It first reads your image as double like yours did. Then it tries to determine "white" pixels by checking that each color channel (of which can be either 1, 3 or 4) is brighter than 0.5. Then your input data points to the clustering will be the x and y "coordinates" (i.e. indices) of your white pixels.
Next it does the clustering via kmeans. This part of the code is loosely based on the already cited answer of Amro. I had to set a large maximal number of iterations, as the problem is ill-posed in the sense that there aren't 10 clear clusters in the picture. Then we compute the mean for each cluster, and plot the clusters with gscatter, and the means with scatter. Note that in order to have the picture facing in the right directions in a scatter plot you have to shift around the input coordinates. Alternatively you could define datax and datay correspondingly at the beginning.
And here's my output, run with the already processed figure you provided in your question:
I do believe you must had made a naive mistake in the plot and that's why you see just a straight line: You are plotting only the x values.
In my opinion, the second argument in the scatter command should be X(cluster1,2) or X(cluster2,2) depending on which scatter command is being used in the code.
The code can be made more simple:
%read file
I=im2double(imread('sil10340.pbm'));
%choose indices of 'white' pixels as coordinates of data
[datax datay]=find(I);
%cluster data into 10 clumps
K = 10; % number of mixtures/clusters
[cInd, c] = kmeans([datax datay], K, 'EmptyAction','singleton',...
'maxiter',1000,'start','cluster');
figure;
gscatter(datay,-datax,cInd); %funky coordinates for plotting according to
image
axis equal;
hold on;
scatter(c(:,2),-c(:,1),20,'ko'); %same funky coordinates
I don't think there is nay need for the looping as the c itself return a 10x2 double array which contains the position of the means

Implementing Naive Bayes Nearest Neighbor (NBNN) in MATLAB

I posted this question on the CV SO a few days ago but it has gone basically unobserved by the forum. I'm trying to implement NBNN in MATLAB to do image classification on the CIFAR-10 image dataset. The algorithm is pretty simple, and I'm confident in it's correctness, however, I'm receiving terrible accuracy rates with 22-28%. I'm unsure why and I'm hoping someone who has experience with image classification or the algorithm could point me in the right direction. The algorithm learns off of SIFT image descriptors, which could be one of the reasons why it's under-performing. Matlab only has a SURF feature detector. From what I've read, SURF/SIFT are basically equivalent. I've been using features, (nDescriptros x 64) obtained from the function below to train my model.
points = detectSURFFeatures(rgb2gray(image));
[features,valid_points] = extractFeatures(image,points);
Another possible issue with the CIFAR dataset and this approach is the small size of the images. Each image is 32 x 32 images which, I beleive, makes feature detection very difficult. I've played around with the different octave settings in the detectSURFFeatures() but nothing has brought my accuracy above 28%.
The annotated code for the approach is below. It's difficult to understand but still might be helpful.
Hopefully someone can help me out.
for i = 3001:4000 %Train on the first 3000 instances and test on the remaining 1000.
closeness = [];
for j = 1:10 %loop over the 10 catergories
class = train(trainLabel==j,:); % Pull out all descriptors that belong to class j
descriptor = test(test_id==i,:); % Pull out all descriptors for image i
[idx,dist] = knnsearch(class,descriptor,'K',1); % Find the distance between the descriptors and the closest labeled descriptor in class j.
total = sum(dist); % sum up distances
closeness = [closeness,sum(total)]; % append a vector of the image-to-class distances.
end
[val,cat] = min(closeness); % Find choose the class that resulted in the lowest, summed distance.
predLabel = [predLabel;cat]; % Append to a vector of labels.
i
end
If your images are 32x32 pixels, then trying to detect interest points is not a good idea. As you have observed, you would get very few features, if any. Upsampling the images is one option. Another option is to use a global descriptor like HOG (extractHOGFeatures).

Irregular Shape Comparison between two inputs

I'm trying to come up with a scoring system for some behavioural psychology research.
I ask people to draw a letter, then trace over it, both on a graphics tablet. I want to assess the accuracy of this trace. So, you draw any letter ('a'), then you do it again, then I score it based on how similar it was to the first time you drew it. The drawings are stored as pixel locations.
Accuracy is assessed as closeness to the original letter. The method does not need to allow for scale, rotation or position changing. Conceptually it's like the area between the two lines, only the lines are highly irregular, so integrals (to my knowledge) wont work.
I'm writing in MATLAB, but any conceptual help would be appreciated. I've tried summing the minimum distance between all pixels drawn on, but this gives good (low) scores to well placed single points.
This must have been done before, but I'm not having any luck with my searches.
--- Partial Solution using method suggested by #Bill below. Doesn't work, as the bwdist gradient is too steep. Rather than the nice second image Bill shows, it looks more like the original.
%% Letter to image
im = zeros(1080,1920,3); % The screen (possible pixel locations)
% A small square a bit like the letter 'a', a couple of pixels wide.
pixthick = 5;
im(450:450+pixthick,[900:1100],:) = 1;
im(550:550+pixthick,[900:1100],:) = 1;
im([450:550],900:900+pixthick,:) = 1;
im([450:570],1100:1100+pixthick,:) = 1;
subplot(2,1,1); imagesc(im); %% atransbw = bwdist(im(:,:,1)<0.5); subplot(2,1,2);
imagesc(atransbw);
Shape contexts are a powerful feature descriptor based on "polar histograms" of the shapes. The Wikipedia page is in-depth, but here is another page with additional information (and a good visual explanation of the technique), as well as MATLAB demo code. Matching letters was one of the original applications of the method, and the demo code I link to doesn't require you to convert your trace vectors to images.
A more simplistic method might be an "image difference" defined as the exclusive-or of two letters. This would require converting your trace vectors to binary images. Something like:
x = xor(im1,im2);
d = sum(x(:)) / sum(im1(:)); %# normalize to the first image
Finally, if your trace vectors have the same number of points, or can be made to by sampling, Procrustes Analysis could be useful. The idea of Procrustes analysis is to find a least-squares optimum linear transformation (rotation, translation and scaling) between two sets of points. Goodness of fit between the two point sets is given by the "Procrustes statistic" or other measures like root-mean-square deviation of the points.
%# Whatever makes sense;
%# procrustes needs N x 2 matrices with (x,y) coords for N points.
coords1 = [x1 y1];
coords2 = [x2 y2];
%# This sampling may be too naive.
n = max( size(coords1,1), size(coords2,1) );
coords1 = coords1(1:n,:);
coords2 = coords2(1:n,:);
%# d is sum-of-squares error
%# z is transformed coords2
%# tr is the linear transformation
[ d, z, tr ] = procrustes( coords1, coords2 );
%# RMS deviation of points may be better than SSE.
n = size(coords1,1);
rmsd = sqrt((sum((coords1(:) - z(:)).^2) / n));
What could help you is a distance transform, implemented in MATLAB as bwdist. This rewards lines being close, even if they don't match.
a_img_1 = imread('a.jpg');
imagesc(a_img_1);
a_img_1_dist_transform = bwdist( a(:, :, 1) < 250 );
imagesc(a_img_1_dist_transform);
You can do the same with the second image, and sum up the difference in pixel values in the distance transformed images, something like:
score = sum( abs( a_img_1_dist_transform(:) - a_img_2_dist_transform(:) ) )
(Note that this will give higher scores to less similar images and v.v.)
To help prevent issues that you mention of "good (low) scores to well placed single points", you could experiment with other distance measures, such as squared distance between pixel values.
You may want to find an affine transform that will match with some error criterion, say mean squared error. This way you will be invariant to translation and scaling. Or if you wanted to penalize translation, you could add the cost of translation as well. (It would help us help you if you give more information on what kind of features do consider similar or otherwise)
Now, an efficient implementation is another matter. Perhaps you should look into image registration. I'm sure this has been done numerous times.
This is my final, overcomplicated solution, which basically uses Bill Cheatham's method. Thanks for all the help!
% pixLet is the 2D vector contain locations where drawing occurred. First convert it to an image.
im = zeros(1000,1000); % This is the image
for pix = 2:size(pixLet,1)
y1 = pixLet(pix-1,2); x1 = pixLet(pix-1,1);
y2 = pixLet(pix,2); x2 = pixLet(pix,1);
xyd = round(pdist([x1 y1; x2 y2])*2);
xs = round(linspace(x1,x2,xyd));
ys = round(linspace(y1,y2,xyd));
for linepix = 1:length(xs)
im(ys(linepix),xs(linepix)) = 1;
end
end
% Blur the image
blur = fspecial('gaussian',[sz sz],reach);
gausIm = conv2(im,blur,'same');
% I made a function of the above to do this for both the template and the trace.
score = sum(sum(abs(gausIm1-gausIm2)));
I would actually suggest a much more high-level solution. Find an OCR machine learning algorithm that returns some kind of confidence. Or, if you don't have confidence, test the distance between the output text and the actual.
This is like a human that watches the handwriting and attempts to understand it. The higher the confidence, the better the result.