Detect endpoints of a line - matlab

I want to detect the points shown in the image below:
I have done this so far:
[X,map] = rgb2ind(img,0.0);
img = ind2gray(X,map); % Convert indexed to grayscale
level = graythresh(img); % Compute an appropriate threshold
img_bw = im2bw(img,level);% Convert grayscale to binary
mask = zeros(size(img_bw));
mask(2:end-2,2:end-2) = 1;
img_bw(mask<1) = 1;
%invert image
img_inv =1-img_bw;
% find blobs
img_blobs = bwmorph(img_inv,'majority',10);
% figure, imshow(img_blobs);
[rows, columns] = size(img_blobs);
for col = 1 : columns
thisColumn = img_blobs(:, col);
topRow = find(thisColumn, 1, 'first');
bottomRow = find(thisColumn, 1, 'last');
img_blobs(topRow : bottomRow, col) = true;
end
inverted = imcomplement(img_blobs);
ed = edge(inverted,'canny');
figure, imshow(ed),title('inverted');
Now how to proceed to get the coordinates of the desired position?

The top point is obviously the white pixel with the highest ordinate, which is easily obtained.
The bottom point is not so well defined. What you can do is
follow the peak edges until you reach a local minimum, on the left and on the right. That gives you a line segment, which you can intersect with the vertical through the top point.
if you know a the peak width, try every pixel on the vertical through the top point, downward, and stop until it has no left nor right neighbors at a distance equal to the peak with.
as above, but stop when the distance between the left and right neighbors exceeds a threshold.

In this particular case, you could consider using houghlines in matlab. Setting the required Theta and MinLength parameter values, you should be able to get the two vertical lines parallel to your peak. You can use the end points of the vertical lines, to get the point at the bottom.
Here is a sample code.
[H,theta,rho] = hough(bw,'Theta',5:1:30);%This is the angle range
P = houghpeaks(H,500,'NHoodSize',[11 11]);
lines = houghlines(bw,theta,rho,P,'FillGap',10,'MinLength',300);
Here is a complete description of how houghlines actually works.

Related

How do i create a rectangular mask at known angles?

I have created a synthetic image that consists of a circle at the centre of a box with the code below.
%# Create a logical image of a circle with image size specified as follows:
imageSizeY = 400;
imageSizeX = 300;
[ygv, xgv] = meshgrid(1:imageSizeY, 1:imageSizeX);
%# Next create a logical mask for the circle with specified radius and center
centerY = imageSizeY/2;
centerX = imageSizeX/2;
radius = 100;
Img = double( (ygv - centerY).^2 + (xgv - centerX).^2 <= radius.^2 );
%# change image labels from double to numeric
for ii = 1:numel(Img)
if Img(ii) == 0
Img(ii) = 2; %change label from 0 to 2
end
end
%# plot image
RI = imref2d(size(Img),[0 size(Img, 2)],[0 size(Img, 1)]);
figure, imshow(Img, RI, [], 'InitialMagnification','fit');
Now, i need to create a rectangular mask (with label == 3, and row/col dimensions: 1 by imageSizeX) across the image from top to bottom and at known angles with the edges of the circle (see attached figure). Also, how can i make the rectangle thicker than 1 by imageSizeX?. As another option, I would love to try having the rectangle stop at say column 350. Lastly, any ideas how I can improve on the resolution? I mean is it possible to keep the image size the same while increasing/decreasing the resolution.
I have no idea how to go about this. Please i need any help/advice/suggestions that i can get. Many thanks!.
You can use the cos function to find the x coordinate with the correct angle phi.
First notice that the angle between the radius that intersects the vertex of phi has angle with the x-axis given by:
and the x coordinate of that vertex is given by
so the mask simply needs to set that row to 3.
Example:
phi = 45; % Desired angle in degrees
width = 350; % Desired width in pixels
height = 50; % Desired height of bar in pixels
theta = pi-phi*pi/180; % The radius angle
x = centerX + round(radius*cos(theta)); % Find the nearest row
x0 = max(1, x-height); % Find where to start the bar
Img(x0:x,1:width)=3;
The resulting image looks like:
Note that the max function is used to deal with the case where the bar thickness would extend beyond the top of the image.
Regarding resolution, the image resolution is determined by the size of the matrix you create. In your example that is (400,300). If you want higher resolution simply increase those numbers. However, if you would like to link the resolution to a higher DPI (Dots per Inch) so there are more pixels in each physical inch you can use the "Export Setup" window in the figure File menu.
Shown here:

Matlab: given 4 points, fit the closest rhombus/square

I need a script that "checks" if 4 given points form a square or a rhombus.
I am working in a QR code segmentation script in which I try to locate the vertex by looking for the non-negative values of a binary image traversing it by rows and columns.
There are some cases in which checking is not neccessary, like in this image:
It is a bit hard to see, but the vertex are labelled as 4 points in green, magenta, cyan and yellow. In this case the script should return the same input points, since no modification is needed.
On the other hand, there are cases in which the vertex are labeled as so:
It can be seen that the magenta and cyan labels rely on the top right corner of the image. This is obviously not correct, but it fullfills the specified condition: traverse each row of the image until you find a row satisfying sum(row)>1 (greater than 1 to avoid single, noisy pixels).
How can I locate the misplaced vertex and place it using the remaining vertex coordinates?
EDIT
Solved the problem. I'm posting the code of the function in case someone needs it:
function correctedCorners = square(corners)
correctedCorners = corners;
X = corners(:,1);
Y = corners(:,2);
sortedX = sort(corners(:,1));
sortedY = sort(corners(:,2));
%% DISTANCES BW POINTS
for i=1:4
for j=1:4
distances(i,j) = sqrt((corners(i,1)-corners(j,1))^2+ (corners(i,2)-corners(j,2))^2);
end
end
%% relationship bw distances
% check corner 1
d11 = distances(1,1);%0
d12 = distances(1,2);%x
d13 = distances(1,3);%sqrt(2)*x
d14 = distances(1,4);%x
bool1 = [(d12*0.8<=d14)&(d12*1.2>=d14) (d12*0.8*sqrt(2)<=d13)& (d12*1.2*sqrt(2)>=d13) (d14*0.8<=d12)&(d14*1.2>=d12) (d14*0.8*sqrt(2)<=d13)&(d14*1.2*sqrt(2)>=d13)];
% check corner 2
d21 = distances(2,1);%x
d22 = distances(2,2);%0
d23 = distances(2,3);%x
d24 = distances(2,4);%sqrt(2)*x
bool2 = [(d21*0.8<=d23)&(d21*1.2>=d23) (d21*0.8*sqrt(2)<=d24)&(d21*1.2*sqrt(2)>=d24) (d23*0.8<=d21)&(d23*1.2>=d21) (d23*0.8*sqrt(2)<=d24)&(d23*1.2*sqrt(2)>=d24)];
% check corner 3
d31 = distances(3,1);%sqrt(2)*x
d32 = distances(3,2);%x
d33 = distances(3,3);%0
d34 = distances(3,4);%x
bool3 = [(d32*0.8<=d34)&(d32*1.2>=d34) (d32*0.8*sqrt(2)<=d31)&(d32*1.2*sqrt(2)>=d31) (d34*0.8<=d32)&(d34*1.2>=d32) (d34*0.8*sqrt(2)<=d31)&(d34*1.2*sqrt(2)>=d31)];
% check corner 4
d41 = distances(4,1);%x
d42 = distances(4,2);%sqrt(2)*x
d43 = distances(4,3);%x
d44 = distances(4,4);%0
bool4 = [(d41*0.8<=d43)&(d41*1.2>=d43) (d41*0.8*sqrt(2)<=d42)&(d41*1.2*sqrt(2)>=d42) (d43*0.8<=d41)&(d43*1.2>=d41) (d43*0.8*sqrt(2)<=d42)&(d43*1.2*sqrt(2)>=d42)];
bool = [bool1; bool2;bool3;bool4];
idx = 0;
for i=1:4
if (sum(bool(i,:))==0)
idx = [idx i];
end
end
if (length(idx)>=2)
for i=2:length(idx)
switch idx(i)
case 1
correctedCorners(1,:) = abs(corners(4,:)-(corners(3,:)-corners(2,:)));
case 2
correctedCorners(2,:) = abs(corners(3,:)-(corners(4,:)-corners(1,:)));
case 3
correctedCorners(3,:) = abs(corners(2,:)+(corners(1,:)-corners(1,:)));
case 4
correctedCorners(4,:) = abs(corners(1,:)+(corners(3,:)-corners(2,:)));
end
end
end
From basic geometry about squares:
TopLeft distance to BotLeft = x
TopLeft distance to TopRight= x
TopLeft distance to BotRight= sqrt(2)*x
Use the same logic for BotLeft to other points, etc.
Allow yourself something like 10-20% margin of error to declare an incorrect point. That is, if TopLeft distance to 2 points outside of the range (80%;120%)*x , and its distance to the third point is outside of the range (80%;120%)*sqrt(2)*x, you can declare the point as placed incorrectly.
In your case, the TopLeft point fails on all distance tests:
0 instead of x to TopRight (about 100% error)
sqrt(2)*x vs x to BotLeft (about 44% error)
x vs sqrt(2)*x to BotRight) (about 31% error)
As long as the rhombus is very similar to a square, a 20% margin of error while treating it as a square should still work.

Matlab manipulate edges

I'm working on an image processing project. I have a grayscale image and detected edges with Canny edge detection. Now I would like to manipulate the result by filtering the unnecessary edges. I would like to keep the edges which are close to horizontal and delete edges which are close to vertical.
How can I delete the close to vertical edges?
One option is to use half of a Sobel operator. The full algorithm finds horizontal and vertical edges, then combines them. You are only interested in horizontal edges, so just compute that part (which is Gy in the Wikipedia article).
You may also want to threshold the result to get a black and white image instead of shades of gray.
You could apply this technique to the original grayscale image or the result of the Canny edge detection.
It depends on how cost-intensive it is allowed to be. One easy way to do would be:
(1) Convolute your image with Sobel-Filters (gives Dx, Dy).
For each canny-edge-pixel:
(2) Normalize (Dx, Dy), s.t. in every pixel you have the direction of your edge.
(3) Compute the inner products with the direction you want to remove (in your case (0,1)).
(4) If the absolut value of the inner product is smaller than some threshold remove the pixel.
Example:
img = ...;
canny_img = ...;
removeDir = [0;1];
% convolute with sobel masks
sobelX = [1, 0, -1; 2, 0, -2; 1, 0, -1];
sobelY = sobelX';
DxImg = conv2(img,sobelX,'same');
DyImg = conv2(img,sobelY,'same');
% for each canny-edge-pixel:
for lin = 1:size(img,1) % <-> y
for col = 1:size(img,2) % <-> x
if canny_img(lin,col)
% normalize direction
normDir = [DxImg(lin,col); DyImg(lin,col)];
normDir = normDir / norm(normDir,2);
% inner product
innerP = normDir' * removeDir;
% remove edge?
if abs(innerP) < cos(45/180*pi) % 45° threshold
canny_img(lin,col) = 0;
end
end
end
end
You can optimize it a lot due to your requirements.

how to scale bounding box coordinates?

I'm extracting the outline of blob the following way:
bw = im2bw(image, threshold);
boundaries = bwboundaries(bw);
plot(boundaries(:, 2), boundaries(:, 1), 'k', 'LineWidth', 2);
what I would like to do now, is to scale boundaries so that I can plot a smaller version of the boundaries inside the original boundaries. Is there an easy way to do this?
Here's an example on what the result should look like: black is the original bounding box, and red is the same bounding box, just scaled (but with same center as black box).
EDIT:
I guess I can scale each point individually, but then I still have to recenter the coordinates. Is there a better way of doing this?
scale = 0.7
nbr_points = size(b, 1);
b_min = nan(nbr_points, 2);
for k = 1 : nbr_points
b_min(k, :) = ([scale 0; 0 scale] * b(k, 1:2)')';
end
Just creating a function which does this should be easy.
function scaledB = scaleBoundaries(B,scaleFactor)
% B is a cell array of boundaries. The output is a cell array
% containing the scaled boundaries, with the same center of mass
% as the input boundaries.
%%
for k = 1:length(B)
scaledB{k} = B{k} .* scaleFactor;
com = mean(B{k}); % Take the center of mass of each boundary
sCom = mean(scaledB{k}); % Take the center of mass of each scaled boundary
difference = com-sCom; % Difference between the centers of mass
% Recenter the scaled boundaries, by adding the difference in the
% centers of mass to the scaled boundaries:
scaledB{k}(:,1) = scaledB{k}(:,1) + difference(1);
scaledB{k}(:,2) = scaledB{k}(:,2) + difference(2);
end
end
Or did you want to avoid something unoptimized for speed purposes?

How to measure the rotation of a image in MATLAB?

I have two images. One is the original, and the another is rotated.
Now, I need to discover the angle that the image was rotated. Until now, I thought about discovering the centroids of each color (as every image I will use has squares with colors in it) and use it to discover how much the image was rotated, but I failed.
I'm using this to discover the centroids and the color in the higher square in the image:
i = rgb2gray(img);
bw = im2bw(i,0.01);
s = regionprops(bw,'Centroid');
centroids = cat(1, s.Centroid);
colors = impixel(img,centroids(1),centroids(2));
top = max(centroids);
topcolor = impixel(img,top(1),top(2));
You can detect the corners of one of the colored rectangles in both the image and the rotated version, and use these as control points to infer the transformation between the two images (like in image registration) using the CP2TFORM function. We can then compute the angle of rotation from the affine transformation matrix:
Here is an example code:
%# read first image (indexed color image)
[I1 map1] = imread('http://i.stack.imgur.com/LwuW3.png');
%# constructed rotated image
deg = -15;
I2 = imrotate(I1, deg, 'bilinear', 'crop');
%# find blue rectangle
BW1 = (I1==2);
BW2 = imrotate(BW1, deg, 'bilinear', 'crop');
%# detect corners in both
p1 = corner(BW1, 'QualityLevel',0.5);
p2 = corner(BW2, 'QualityLevel',0.5);
%# sort corners coordinates in a consistent way (counter-clockwise)
p1 = sortrows(p1,[2 1]);
p2 = sortrows(p2,[2 1]);
idx = convhull(p1(:,1), p1(:,2)); p1 = p1(idx(1:end-1),:);
idx = convhull(p2(:,1), p2(:,2)); p2 = p2(idx(1:end-1),:);
%# make sure we have the same number of corner points
sz = min(size(p1,1),size(p2,1));
p1 = p1(1:sz,:); p2 = p2(1:sz,:);
%# infer transformation from corner points
t = cp2tform(p2,p1,'nonreflective similarity'); %# 'affine'
%# rotate image to match the other
II2 = imtransform(I2, t, 'XData',[1 size(I1,2)], 'YData',[1 size(I1,1)]);
%# recover affine transformation params (translation, rotation, scale)
ss = t.tdata.Tinv(2,1);
sc = t.tdata.Tinv(1,1);
tx = t.tdata.Tinv(3,1);
ty = t.tdata.Tinv(3,2);
translation = [tx ty];
scale = sqrt(ss*ss + sc*sc);
rotation = atan2(ss,sc)*180/pi;
%# plot the results
subplot(311), imshow(I1,map1), title('I1')
hold on, plot(p1(:,1),p1(:,2),'go')
subplot(312), imshow(I2,map1), title('I2')
hold on, plot(p2(:,1),p2(:,2),'go')
subplot(313), imshow(II2,map1)
title(sprintf('recovered angle = %g',rotation))
If you can identify a color corresponding to only one component it is easier to:
Calculate the centroids for each image
Calculate the mean of the centroids (in x and y) for each image. This is the "center" of each image
Get the red component color centroid (in your example) for each image
Subtract the mean of the centroids for each image from the red component color centroid for each image
Calculate the ArcTan2 for each of the vectors calculated in 4), and subtract the angles. That is your result.
If you have more than one figure of each color, you need to calculate all possible combinations for the rotation and then select the one that is compatible with the other possible rotations.
I could post the code in Mathematica, if you think it is useful.
I would take a variant to the above mentioned approach:
% Crude binarization method to knock out background and retain foreground
% features. Note one looses the cube in the middle
im = im > 1
Then I would get the 2D autocorrelation:
acf = normxcorr2(im, im);
From this result, one can easily detect the peaks, and as rotation carries into the autocorrelation function (ACF) domain, one can ascertain the rotation by matching the peaks between the original ACF and the ACF from the rotated image, for example using the so-called Hungarian algorithm.