Find the circle on the experimental image (Matlab) - matlab

I have a series of the experimental images. In order to process it, at first, I need to determine the area of interest. This area is between two concentric circles. These circles can be a little different on each image (e.g. center can be shifted by a small distance).
In order to find these circles, I converted my image to binary image. Now it looks like this:
I am interested in the biggest circle and the one after the biggest (See note on the picture). Does anyone know a fast algorithm that can find it? Exhausted search seems to be a bad option. I know, that at each image the locations and radii of these circles can change only by a little. Here is a link to .mat file with the picture I attached link.
Thanks,

You can achieve this task even without using inbuilt function imfindcircles. I follow the following method. Get all the non-zero pixels using find. Now you have all the points, I want to classify them into different circles, for this I will use histogram. I can fix the number of bins (which is number of circles) here. Once I got all the points separated, I have scattered points along a circle..with these points, I will fit a circle along those points. You may check the below code:
load Sample.mat ;
I = Immm ;
[y,x] = find(I) ;
%% Get Bounding box
x0 = min(x) ; x1 = max(x) ;
y0 = min(y) ; y1 = max(y) ;
% length and breadth
L = abs(x1-x0) ;
B = abs(y1-y0) ;
% center bounding box
C = [x0+B/2 y0+L/2] ;
%% Get distances of the points from center of bounding box
data = repmat(C,[length(x),1])-[x,y] ;
dist = sqrt(data(:,1).^2+data(:,2).^2);
%% Classify the points into circles
nbins = 4 ; % number of circles you want
[N,edges,bin] = histcounts(dist,nbins) ;
% plot the classified circle points for check
figure(1)
imshow(I)
hold on
for i = 1:nbins
plot((x(bin==i)),y(bin==i),'.','color',rand(1,3)) ;
end
%% Circle's radii and center
Circ = cell(nbins,1) ;
for i = 1:nbins
[xc1,yc1,R] = circfit(x(bin==i),y(bin==i)) ;
Circ{i} = [xc1 yc1 R] ;
end
figure(2)
imshow(I)
hold on
th = linspace(0,2*pi) ;
for i = 1:nbins
xc = Circ{i}(1)+Circ{i}(3)*cos(th) ;
yc = Circ{i}(2)+Circ{i}(3)*sin(th) ;
plot(xc,yc,'color',rand(1,3),'linewidth',3) ;
end
Circ is cell, which centers and Radii of circles. You can download the function circfit from this link: http://matlab.wikia.com/wiki/FAQ#How_can_I_fit_a_circle_to_a_set_of_XY_data.3F

Related

How can I reduce the number of mesh lines shown in a surface plot?

I've found this answer, but I can't complete my work. I wanted to plot more precisely the functions I am studying, without overcoloring my function with black ink... meaning reducing the number of mesh lines. I precise that the functions are complex.
I tried to add to my already existing code the work written at the link above.
This is what I've done:
r = (0:0.35:15)'; % create a matrix of complex inputs
theta = pi*(-2:0.04:2);
z = r*exp(1i*theta);
w = z.^2;
figure('Name','Graphique complexe','units','normalized','outerposition',[0.08 0.1 0.8 0.55]);
s = surf(real(z),imag(z),imag(w),real(w)); % visualize the complex function using surf
s.EdgeColor = 'none';
x=s.XData;
y=s.YData;
z=s.ZData;
x=x(1,:);
y=y(:,1);
% Divide the lengths by the number of lines needed
xnumlines = 10; % 10 lines
ynumlines = 10; % 10 partitions
xspacing = round(length(x)/xnumlines);
yspacing = round(length(y)/ynumlines);
hold on
for i = 1:yspacing:length(y)
Y1 = y(i)*ones(size(x)); % a constant vector
Z1 = z(i,:);
plot3(x,Y1,Z1,'-k');
end
% Plotting lines in the Y-Z plane
for i = 1:xspacing:length(x)
X2 = x(i)*ones(size(y)); % a constant vector
Z2 = z(:,i);
plot3(X2,y,Z2,'-k');
end
hold off
But the problem is that the mesh is still invisible. How to fix this? Where is the problem?
And maybe, instead of drawing a grid, perhaps it is possible to draw circles and radiuses like originally on the graph?
I found an old script of mine where I did more or less what you're looking for. I adapted it to the radial plot you have here.
There are two tricks in this script:
The surface plot contains all the data, but because there is no mesh drawn, it is hard to see the details in this surface (your data is quite smooth, this is particularly true for a more bumpy surface, so I added some noise to the data to show this off). To improve the visibility, we use interpolation for the color, and add a light source.
The mesh drawn is a subsampled version of the original data. Because the original data is radial, the XData and YData properties are not a rectangular grid, and therefore one cannot just take the first row and column of these arrays. Instead, we use the full matrices, but subsample rows for drawing the circles and subsample columns for drawing the radii.
% create a matrix of complex inputs
% (similar to OP, but with more data points)
r = linspace(0,15,101).';
theta = linspace(-pi,pi,101);
z = r * exp(1i*theta);
w = z.^2;
figure, hold on
% visualize the complex function using surf
% (similar to OP, but with a little bit of noise added to Z)
s = surf(real(z),imag(z),imag(w)+5*rand(size(w)),real(w));
s.EdgeColor = 'none';
s.FaceColor = 'interp';
% get data back from figure
x = s.XData;
y = s.YData;
z = s.ZData;
% draw circles -- loop written to make sure the outer circle is drawn
for ii=size(x,1):-10:1
plot3(x(ii,:),y(ii,:),z(ii,:),'k-');
end
% draw radii
for ii=1:5:size(x,2)
plot3(x(:,ii),y(:,ii),z(:,ii),'k-');
end
% set axis properties for better 3D viewing of data
set(gca,'box','on','projection','perspective')
set(gca,'DataAspectRatio',[1,1,40])
view(-10,26)
% add lighting
h = camlight('left');
lighting gouraud
material dull
How about this approach?
[X,Y,Z] = peaks(500) ;
surf(X,Y,Z) ;
shading interp ;
colorbar
hold on
miss = 10 ; % enter the number of lines you want to miss
plot3(X(1:miss:end,1:miss:end),Y(1:miss:end,1:miss:end),Z(1:miss:end,1:miss:end),'k') ;
plot3(X(1:miss:end,1:miss:end)',Y(1:miss:end,1:miss:end)',Z(1:miss:end,1:miss:end)','k') ;

mean value in a sphere

I'm trying to calculate the mean value of the pixels inside a circle. In the future this needs to be extended to 3D, but for now a 2D sollution would already help me out.
As can be seen in the image, some pixels are entirely inside the circle, but some are only partly inside the circle. The ones partly in the circle also need to contribute only partly to the mean value. The pixels are square. This will simplify the mathematics I hope.
I can calculate the distance from the pixelcorners to the central point, from this you can find the pixels enterly inside and enterly outside. The rest needs correction. But how to find this correction.
[edit] thanks to Heath Raftery the problem is solved! [/edit]
the integral of a circle with radius r
As an example: I want to know the average pixelvalue of pixels in this circle. I know it is 0.3425, since 34.25% of the circle has a value of 1 and the rest is 0.
Function to check what part of a pixel is in the circle:
function [ a ] = incirc( x,y,r )
%only handles the top right quadrant of a circle
if x<0||y<0,error('only positive x,y');end
%integral of sqrt(r^2-x^2) dx
F = #(x,r) (1/2)*(x*sqrt(r^2-x^2)+r^2*atan(x/sqrt(r^2-x^2)));
%find corner locations
x=[x-0.5,x+0.5];
y=[y-0.5,y+0.5];
d = sqrt(x.^2+y.^2); %distance to closed and furthest corner
if max(d)<r,a=1;return;end %inside circle
if min(d)>r,a=0;return;end %outside circle
%intersections with edges (r^2 = x^2+y^2)
inters = [sqrt(r^2-y(1)^2),sqrt(r^2-y(2)^2),sqrt(r^2-x(1)^2),sqrt(r^2-x(2)^2)]; %x(1) x(2) y(1) y(2)
%remove imaginary and out of range intersections
inters(imag(inters)~=0)=NaN;
inters(inters<1E-5)=NaN; %to find values that are zero
inters([~((x(1)<inters(1:2))&(inters(1:2)<x(2))),~((y(1)<inters(3:4))&(inters(3:4)<y(2)))])=NaN;
idx = find(~isnan(inters));
if numel(idx)~=2,error('need two intersections of circle with pixel');end
%check area of pixel inside circumference
if all(idx==[1,2]) %2 intersections on y-edge
a=(F(y(2),r)-F(y(1),r)) - x(1); %area
elseif all(idx==[3,4]) %2 intersections on x-edge
a=(F(x(2),r)-F(x(1),r)) - y(1); %area
elseif all(idx==[1,3]) %one intersection on y-edge one on x-edge (left&bottom)
a=(F(inters(1),r)-F(x(1),r))- (y(1)*(inters(1)-x(1)));
elseif all(idx==[2,4]) %one intersection on y-edge one on x-edge (top&right)
a=(inters(2)-x(1))+(F(x(2),r)-F(inters(2),r))-(y(1)*(x(2)-inters(2)));
else
error('geometry')
end
a=real(a);
if a<0||a>1
error('computational error');
end
end
Script to test the function
M = ones(100); %data
M(1:50,:)=0;
pos=[50.2,50];
r = 2;
%calculate what the result should be
h=50-pos(2)+0.5;
A=pi*r^2;
wedge = acos(h/r)/pi;
triangle = h*sqrt(r^2-h^2);
res=(A*wedge-triangle)/A
S=0;N=0;
for i = 1:size(M,1)
for j = 1:size(M,2)
x=abs(j-pos(1));
y=abs(i-pos(2));
n=incirc( x,y,r );
M_(i,j)=n;
S = S+M(i,j)*n;
N = N+n;
end
end
result = S/N
result = 0.3425
You can see the algorithm finds the part of the pixel in the circle.
The question is missing a question, but I'll assume that it's not how to calculate whether pixels are fully inside or outside the circle. That's a relatively simple task. That is, a pixel is fully inside if the furtherest corner of the pixel to the centre is less than a radius away from the centre, and a pixel is fully outside if the closest corner of the pixel to the centre is more than a radius away from the centre.
The question of what proportion of pixels on the circumference fall within the circumference is much trickier. There are two fundamental solutions:
Exact and hard.
Approximate and a bit easier.
In both cases, note the horizontal and vertical symmetry means only the top right quadrant need be considered.
Then, for (1), translate the circle centre to the origin (0, 0) and treat the circumference as the function y(x) = sqrt(r^2 - x^2). Then, the area of an overlapping pixel within the circle is the integral:
integral(y(x) - y0, from x0 to x1, with respect to x)
where y0 is the bottom coordinate of the pixel, x0 is the left coordinate and x1 is the right coordinate.
This integral can be solved exactly with a trigonometric identity and a trigonometric substitution.
For (2), just generate a set of random points within the pixel and count how many of them fall within the circumference. As the set gets larger, the proportion of points that fall within the circumference to the count of all point approaches the proportion of the pixel within the circumference.
You can use inpolygon, to get the indices which lie inside the circle, once you have those indices you can get your pixels and do what you want.
M = rand(100); %data
[nx,ny] = size(M) ;
[X,Y] = meshgrid(1:ny,1:nx) ;
pos=[20,20];
r = 5;
phi=linspace(0,2*pi,100);
imagesc(M);
axis image
hold on
plot(pos(1),pos(2),'rx')
xc = pos(1)+r*sin(phi) ;
yc = pos(2)+r*cos(phi) ;
plot(xc,yc,'-r');
% hold off
%% get indices which are inside the circle
idx = inpolygon(X(:),Y(:),xc,yc) ;
xi = X(idx) ; yi = Y(idx) ;
plot(xi,yi,'.r')
mypixels = M(idx) ;
You can also use rangesearch to get the points lying within the given radius of the circle. As below:
M = rand(100); %data
[nx,ny] = size(M) ;
[X,Y] = meshgrid(1:ny,1:nx) ;
pos=[20,20];
r = 5;
phi=linspace(0,2*pi,100);
imagesc(M);
axis image
hold on
plot(pos(1),pos(2),'rx')
xc = pos(1)+r*sin(phi) ;
yc = pos(2)+r*cos(phi) ;
plot(xc,yc,'-r');
% hold off
%% Use nearest neighbour search
idx = rangesearch([X(:),Y(:)],pos,r) ;
xi = X(idx{1}) ; yi = Y(idx{1}) ;
plot(xi,yi,'.r')
mypixels = M(idx{1}) ;

How to to identify letters on a license plate with varying perspectives

I am making a script in Matlab that takes in an image of the rear of a car. After some image processing I would like to output the original image of the car with a rectangle around the license plate of the car. Here is what I have written so far:
origImg = imread('CAR_IMAGE.jpg');
I = imresize(origImg, [500, NaN]); % easier viewing and edge connecting
G = rgb2gray(I);
M = imgaussfilt(G); % blur to remove some noise
E = edge(M, 'Canny', 0.4);
% I can assume all letters are somewhat upright
RP = regionprops(E, 'PixelIdxList', 'BoundingBox');
W = vertcat(RP.BoundingBox); W = W(:,3); % get the widths of the BBs
H = vertcat(RP.BoundingBox); H = H(:,4); % get the heights of the BBs
FATTIES = W > H; % find the BBs that are more wide than tall
RP = RP(FATTIES);
E(vertcat(RP.PixelIdxList)) = false; % remove more wide than tall regions
D = imdilate(E, strel('disk', 1)); % dilate for easier viewing
figure();
imshowpair(I, D, 'montage'); % display original image and processed image
Here are some examples:
From here I am unsure how to isolate the letters of the license plate, particularly like in the second example above where each letter has a decreased area due to the perspective of the image. My first idea was to get the bounding box of all regions and keep only the regions where the perimeter to area ratio is "similar" but this resulted in removing the letters of the plate that were connected when I dilate the image like the K and V in the fourth example above.
I would appreciate some suggestions on how I should go about isolating these letters. No code is necessary, and any advice is appreciated.
So I continued to work despite not receiving any answers here on SO and managed to get a working version through trial and error. All of the following code comes after the code in my original question and all plots below are from the first example image above. First, I found the variance for every single pixel row of the image and plotted them like so:
V = var(D, 0, 2);
X = 1:length(V);
figure();
hold on;
scatter(X, V);
I then fit a very high order polynomial to this scatter plot and saved the values where the slope of the polynomial was zero and the variance value was very low (i.e. the dark row of pixels immediately before or after a row with some white):
P = polyfit(X', V, 25);
PV = polyval(P, X);
Z = X(find(PV < 0.03 & abs(gradient(PV)) < 0.0001));
plot(X, PV); % red curve on plot
scatter(Z, zeros(1,length(Z))); % orange circles on x-axis
I then calculate the integral of the polynomial between any consecutive Z values (my dark rows), and save the two Z values between which the integral is the largest, which I mark with lines on the plot:
MAX_INTEG = -1;
MIN_ROW = -1;
MAX_ROW = -1;
for i = 1:(length(Z)-1)
TEMP_MIN = Z(i);
TEMP_MAX = Z(i+1);
Q = polyint(P);
TEMP_INTEG = diff(polyval(Q, [TEMP_MIN, TEMP_MAX]));
if (TEMP_INTEG > MAX_INTEG)
MAX_INTEG = TEMP_INTEG;
MIN_ROW = TEMP_MIN;
MAX_ROW = TEMP_MAX;
end
end
line([MIN_ROW, MIN_ROW], [-0.1, max(V)+0.1]);
line([MAX_ROW, MAX_ROW], [-0.1, max(V)+0.1]);
hold off;
Since the X-values of these lines correspond row numbers in the original image, I can crop my image between MIN_ROW and MAX_ROW:
I repeat the above steps now for the columns of pixels, crop, and remove any excess black rows of columns to result in the identified plate:
I then perform 2D cross correlation between this cropped image and the edged image D using Matlab's xcorr2 to locate the plate in the original image. After finding the location I just draw a rectangle around the discovered plate like so:

Find the real time co-ordinates of the four points marked in red in the image

To be exact I need the four end points of the road in the image below.
I used find[x y]. It does not provide satisfying result in real time.
I'm assuming the images are already annotated. In this case we just find the marked points and extract coordinates (if you need to find the red points dynamically through code, this won't work at all)
The first thing you have to do is find a good feature to use for segmentation. See my SO answer here what-should-i-use-hsv-hsb-or-rgb-and-why for code and details. That produces the following image:
we can see that saturation (and a few others) are good candidate colors spaces. So now you must transfer your image to the new color space and do thresholding to find your points.
Points are obtained using matlab's region properties looking specifically for the centroid. At that point you are done.
Here is complete code and results
im = imread('http://i.stack.imgur.com/eajRb.jpg');
HUE = 1;
SATURATION = 2;
BRIGHTNESS = 3;
%see https://stackoverflow.com/questions/30022377/what-should-i-use-hsv-hsb-or-rgb-and-why/30036455#30036455
ViewColoredSpaces(im)
%convert image to hsv
him = rgb2hsv(im);
%threshold, all rows, all columns,
my_threshold = 0.8; %determined empirically
thresh_sat = him(:,:,SATURATION) > my_threshold;
%remove small blobs using a 3 pixel disk
se = strel('disk',3');
cleaned_sat = imopen(thresh_sat, se);% imopen = imdilate(imerode(im,se),se)
%find the centroids of the remaining blobs
s = regionprops(cleaned_sat, 'centroid');
centroids = cat(1, s.Centroid);
%plot the results
figure();
subplot(2,2,1) ;imshow(thresh_sat) ;title('Thresholded saturation channel')
subplot(2,2,2) ;imshow(cleaned_sat);title('After morpphological opening')
subplot(2,2,3:4);imshow(im) ;title('Annotated img')
hold on
for (curr_centroid = 1:1:size(centroids,1))
%prints coordinate
x = round(centroids(curr_centroid,1));
y = round(centroids(curr_centroid,2));
text(x,y,sprintf('[%d,%d]',x,y),'Color','y');
end
%plots centroids
scatter(centroids(:,1),centroids(:,2),[],'y')
hold off
%prints out centroids
centroids
centroids =
7.4593 143.0000
383.0000 87.9911
435.3106 355.9255
494.6491 91.1491
Some sample code would make it much easier to tailor a specific solution to your problem.
One solution to this general problem is using impoint.
Something like
h = figure();
ax = gca;
% ... drawing your image
points = {};
points = [points; impoint(ax,initialX,initialY)];
% ... generate more points
indx = 1 % or whatever point you care about
[currentX,currentY] = getPosition(points{indx});
should do the trick.
Edit: First argument of impoint is an axis object, not a figure object.

How to draw a straight across the centroid points of the barcode using best fit points Matlab

This is the processed image and I can't increase the bwareaopen() as it won't work for my other image.
Anyway I'm trying to find the shortest points in the centre points of the barcode, to get the straight line across the centre points in the barcode.
Example:
After doing a centroid command, the points in the barcode are near to each other. Therefore, I just wanted to get the shortest points(which is the barcode) and draw a straight line across.
All the points need not be join, best fit points will do.
Step 1
Step 2
Step 3
If you dont have the x,y elements Andrey uses, you can find them by segmenting the image and using a naive threshold value on the area to avoid including the number below the bar code.
I've hacked out a solution in MATLAB doing the following:
Loading the image and making it binary
Extracting all connected components using bwlabel().
Getting useful information about each of them via regionprops() [.centroid will be a good approximation to the middel point for the lines].
Thresholded out small regions (noise and numbers)
Extracted x,y coordinates
Used Andreys linear fit solution
Code:
set(0,'DefaultFigureWindowStyle','docked');
close all;clear all;clc;
Im = imread('29ekeap.jpg');
Im=rgb2gray(Im);
%%
%Make binary
temp = zeros(size(Im));
temp(Im > mean(Im(:)))=1;
Im = temp;
%Visualize
f1 = figure(1);
imagesc(Im);colormap(gray);
%Find connected components
LabelIm = bwlabel(Im);
RegionInfo = regionprops(LabelIm);
%Remove background region
RegionInfo(1) = [];
%Get average area of regions
AvgArea = mean([RegionInfo(1:end).Area]);
%Vector to keep track of likely "bar elements"
Bar = zeros(length(RegionInfo),1);
%Iterate over regions, plot centroids if area is big enough
for i=1:length(RegionInfo)
if RegionInfo(i).Area > AvgArea
hold on;
plot(RegionInfo(i).Centroid(1),RegionInfo(i).Centroid(2),'r*')
Bar(i) = 1;
end
end
%Extract x,y points for interpolation
X = [RegionInfo(Bar==1).Centroid];
X = reshape(X,2,length(X)/2);
x = X(1,:);
y = X(2,:);
%Plot line according to Andrey
p = polyfit(x,y,1);
xMin = min(x(:));
xMax = max(x(:));
xRange = xMin:0.01:xMax;
yRange = p(1).*xRange + p(2);
plot(xRange,yRange,'LineWidth',2,'Color',[0.9 0.2 0.2]);
The result is a pretty good fitted line. You should be able to extend it to the ends by using the 'p' polynomal and evaluate when you dont encounter any more '1's if needed.
Result:
If you already found the x,y of the centers, you should use polyfit function:
You will then find the polynomial coefficients of the best line. In order to draw a segment, you can take the minimal and maximal x
p = polyfit(x,y,1);
xMin = min(x(:));
xMax = max(x(:));
xRange = xMin:0.01:xMax;
yRange = p(1).*xRange + p(2);
plot(xRange,yRange);
If your ultimate goal is to generate a line perpendicular to the bars in the bar code and passing roughly through the centroids of the bars, then I have another option for you to consider...
A simple solution would be to perform a Hough transform to detect the primary orientation of lines in the bar code. Once you find the angle of the lines in the bar code, all you have to do is rotate that by 90 degrees to get the slope of a perpendicular line. The centroid of the entire bar code can then be used as an intercept for this line. Using the functions HOUGH and HOUGHPEAKS from the Image Processing Toolbox, here's the code starting with a cropped version of your image from step 1:
img = imread('bar_code.jpg'); %# Load the image
img = im2bw(img); %# Convert from RGB to BW
[H, theta, rho] = hough(img); %# Perform the Hough transform
peak = houghpeaks(H); %# Find the peak pt in the Hough transform
barAngle = theta(peak(2)); %# Find the angle of the bars
slope = -tan(pi*(barAngle + 90)/180); %# Compute the perpendicular line slope
[y, x] = find(img); %# Find the coordinates of all the white image points
xMean = mean(x); %# Find the x centroid of the bar code
yMean = mean(y); %# Find the y centroid of the bar code
xLine = 1:size(img,2); %# X points of perpendicular line
yLine = slope.*(xLine - xMean) + yMean; %# Y points of perpendicular line
imshow(img); %# Plot bar code image
hold on; %# Add to the plot
plot(xMean, yMean, 'r*'); %# Plot the bar code centroid
plot(xLine, yLine, 'r'); %# Plot the perpendicular line
And here's the resulting image: