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

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.

Related

Why can't I colour my segmented region from the original image

I have the following code:
close all;
star = imread('/Users/name/Desktop/folder/pics/OnTheBeach.png');
blrtype = fspecial('average',[3 3]);
blurred = imfilter(star, blrtype);
[rows,cols,planes] = size(star);
R = star(:,:,1); G = star(:,:,2); B = star(:,:,3);
starS = zeros(rows,cols);
ind = find(R > 190 & R < 240 & G > 100 & G < 170 & B > 20 & B < 160);
starS(ind) = 1;
K = imfill(starS,'holes');
stats = regionprops(logical(K), 'Area', 'Solidity');
ind = ([stats.Area] > 250 & [stats.Solidity] > 0.1);
L = bwlabel(K);
result = ismember(L,find(ind));
Up to this point I load an image, blur to filter out some noise, do colour segmentation to find the specific objects which fall in that range, then create a binary image that has value 1 for the object's colour, and 0 for all other stuff. Finally I do region filtering to remove any clutter that was left in the image so I'm only left with the objects I'm looking for.
Now I want to recolour the original image based on the segmentation mask to change the colour of the starfish. I want to create Red,Green,Blue channels, assign value to them then lay the mask over the image. (To have red starfishes for example)
red = star;
red(starS) = starS(:,:,255);
green = star;
green(starS) = starS(:,:,0);
blue = star;
blue(starS) = star(:,:,0);
out = cat(3, red, green, blue);
imshow(out);
This gives me an error: Index exceeds matrix dimensions.
Error in Project4 (line 28)
red(starS) = starS(:,:,255);
What is wrong with my current approach?
Your code is kinda confusing... I don't understand whether the mask you want to use is starS or result since both look like 2d indexers. In your second code snippet you used starS, but the mask you posted in your question is result.
Anyway, no matter what your desired mask is, all you have to do is to use the imoverlay function. Here is a small example based on your code:
out = imoverlay(star,result,[1 0 0]);
imshow(out);
and here is the output:
If the opaque mask of imoverlay suggested by Tommaso is not what you're after, you can modify the RGB values of the input to cast a hue over the selected pixels without saturating them. It is only slightly more involved.
I = find(result);
gives you an index of the pixels in the 2D image. However, star is 3D. Those indices will point at the same pixels, but only at the first 2D slice. That is, if I points at pixel (x,y), it is equivalently pointing to pixel (x,y,1). That is the red component of the pixel. To index (x,y,2) and (x,y,2), the green and blue components, you need to increment I by numel(result) and 2*numel(result). That is, star(I) accesses the red component of the selected pixels, star(I+numel(result)) accesses the green component, and star(I+2*numel(result)) accesses the blue component.
Now that we can access these values, how do we modify their color?
This is what imoverlay does:
I = find(result);
out = star;
out(I) = 255; % red channel
I = I + numel(result);
out(I) = 0; % green channel
I = I + numel(result);
out(I) = 0; % blue channel
Instead, you can increase the brightness of the red proportionally, and decrease the green and blue. This will change the hue, increase saturation, and preserve the changes in intensity within the stars. I suggest the gamma function, because it will not cause strong saturation artefacts:
I = find(result);
out = double(star)/255;
out(I) = out(I).^0.5; % red channel
I = I + numel(result);
out(I) = out(I).^1.5; % green channel
I = I + numel(result);
out(I) = out(I).^1.5; % blue channel
imshow(out)
By increasing the 1.5 and decreasing the 0.5 you can make the effect stronger.

Detect endpoints of a line

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.

loop over array matrix matlab

I am detecting circles in an image. I return circle radii and X,Y of the axis. I know how to crop 1 circle no problem with formula:
X-radius, Y-radius, width=2*r,height=2*r using imcrop.
My problem is when I get returned more than 1 circle.
I get returned circle radii in an array radiiarray.
I get returned circle centers in centarray.
When i disp(centarray), It looks like this:
146.4930 144.4943
610.0317 142.1734
When I check size(centarray) and disp it i get:
2 2
So I understand first column is X and second is Y axis values. So first circle center would be 146,144.
I made a loop that works for only 1 circle. "-------" is where I'm unsure what to use to get:
note: radius = r
1st circle)
X = centarray(1)-r;
Y = centarray(3)-r;
Width =2*r;
Width =2*r;
2nd circle)
X = centarray(2);
Y = centarray(4);
Width =2*r;
Width =2*r;
How would I modify the "------" parts for my code? I also would like that if there are 3+ circles the loop would work as Im getting sometimes up to 9 circles from an image.
B = imread('p5.tif');
centarray = [];
centarray = [centarray,centers];
radiiarray = [];
radiiarray = [radiiarray,radii];
for j=1:length(radiiarray)
x = centarray((------))-radiiarray(j); %X value to crop
y = centarray((------))-radiiarray(j); %Y value to crop
width = 2*radiiarray(j); %WIDTH
height = 2*radiiarray(j); %HEIGHT
K = imcrop(B, [x y width height]);
end
My full code, which doesnt work, as I realized why when i saw the way values are stored...:
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% DETECT + GET X Y WIDTH HEIGHT OF CIRCLES
I = imread('p5.tif');
subplot(2,2,1);imshow(I);title('Original Image');
%sharpen edges
B = imsharpen(I);
subplot(2,2,2);imshow(B);title('sharpened edges');
%find circles
Img = im2bw(B(:,:,3));
minRad = 20;
maxRad = 90;
[centers, radii] = imfindcircles(Img, [minRad maxRad], ...
'ObjectPolarity','bright','sensitivity',0.84);
imagesc(Img);
viscircles(centers, radii,'Color','green');
%nuber of circles found
%arrays to store values for radii and centers
centarray = [];
centarray = [centarray,centers];
radiiarray = [];
radiiarray = [radiiarray,radii];
sc = size(centarray);
disp(sc)
disp(centarray)
disp(radiiarray)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%CROP USING VALUE FROM ARRAYS NUMBER OF TIMES THERE ARE CENTERS(number of
%circles)
for j=1:length(radiiarray)
x = centarray((2*j)-1)-radiiarray(j); %X value to crop
y = centarray((2*j))-radiiarray(j); %Y value to crop
width = 2*radiiarray(j); %WIDTH
height = 2*radiiarray(j); %HEIGHT
disp(x)
disp(y)
disp(centarray)
%crop using values
K = imcrop(B, [x y width height]);
%togray
gray = rgb2gray(K);
subplot(2,2,3);imshow(K);title('cropped before bw');
Icorrected = imtophat(gray, strel('disk', 15));
%to black and white
black = im2bw(Icorrected);
subplot(2,2,4);imshow(black);title('sharpened edges');
%read
results = ocr(black);
number = results.Text;
%display value
disp(number)
end
Any help on how to create this kind of loop is appreciated as I just have no more ideas or cant find answer to this..
EDIT
SOLUTION
Hi, answer is to treat matrix as 2 dimensional.
for j=1:length(radiiarray)
x=centarray(j,1)
y=centarray(j,2)
width = radiiarray(j)
height = radiiarray(j)
end
as j increases values update correctly now.
answer is to treat matrix as 2 dimensional.
for j=1:length(radiiarray)
x=centarray(j,1)
y=centarray(j,2)
width = radiiarray(j)
height = radiiarray(j)
end
as j increases values update correctly now.
Thanks for #beaker for his comment! Thats why I figured it out

Trouble with the assignment of values to pixels

I'm currently trying to write a function in MatLab which loops over each pixel, takes the mean intensity of the pixels within a radius around it and then applies that intensity to the central pixel, effectively blurring the image.
I start by declaring the function and finding the maximum width and height of the image, nx and ny:
function [] = immean(IMAGE, r)
[nx, ny] = size(IMAGE);
I then create a completely black image of the same size as the image variable IMAGE. This is so that I can store the value of each pixel, once the mean intensity of its neighbourhood has been found.
average = zeros(size(IMAGE));
I then loop through the image:
for x = 1:nx
for y = 1:ny
and apply a series of if-statements to deal with cases where the radius of the circle around the pixel does not fit the image. (For example, a pixel at (1,1) with a radius of 5 would have a starting point of -4, which would cause an error):
if x-r <= 0
startx = 1;
else
startx = x-r;
end
if x+r > nx
endx = nx;
else
endx = x+r;
end
if y-r <= 0
starty = 1;
else
starty = y-r;
end
if y+r > ny
endy = ny;
else
endy = y+r;
end
This effectively creates a square of values that may fall under the domain of the circular sample, which speeds up the program dramatically. After that, I iterate through the values within this square and find any pixels which fall within the radius of the central pixel. The intensities of these pixels are then added to a variable called total and the count pixelcount increments:
total = 0;
pixelcount = 0;
for xp = startx : endx
for yp = starty : endy
if (x-xp)^2 + (y-yp)^2 <= r^2
total = total + uint64(IMAGE(xp, yp));
pixelcount = pixelcount + 1;
end
end
end
I then find the mean intensity of the circular sample of pixels, by dividing total by pixelcount and then plug that value into the appropriate pixel of the completely black image average:
mean = total / pixelcount;
average(x,y) = mean;
The trouble is: this isn't working. Instead of a blurred version of the original image, I get an entirely white image instead. I'm not sure why - when I take the ; from the last line, it shows me that mean constitutes many values - it's not like they're all 255. So I figure that there must be something wrong with the assignment line average(x,y) = mean;, but I can't find out what that is.
Can anyone see why this is going wrong?

Rotation of image manually in matlab

I am trying to rotate the image manually using the following code.
clc;
m1 = imread('owl','pgm'); % a simple gray scale image of order 260 X 200
newImg = zeros(500,500);
newImg = int16(newImg);
rotationMatrix45 = [cos((pi/4)) -sin((pi/4)); sin((pi/4)) cos((pi/4))];
for x = 1:size(m1,1)
for y = 1:size(m1,2)
point =[x;y] ;
product = rotationMatrix45 * point;
product = int16(product);
newx =product(1,1);
newy=product(2,1);
newImg(newx,newy) = m1(x,y);
end
end
imshow(newImg);
Simply I am iterating through every pixel of image m1, multiplying m1(x,y) with rotation matrix, I get x',y', and storing the value of m1(x,y) in to `newImg(x',y')' BUT it is giving the following error
??? Attempted to access newImg(0,1); index must be a positive integer or logical.
Error in ==> at 18
newImg(newx,newy) = m1(x,y);
I don't know what I am doing wrong.
Part of the rotated image will get negative (or zero) newx and newy values since the corners will rotate out of the original image coordinates. You can't assign a value to newImg if newx or newy is nonpositive; those aren't valid matrix indices. One solution would be to check for this situation and skip such pixels (with continue)
Another solution would be to enlarge the newImg sufficiently, but that will require a slightly more complicated transformation.
This is assuming that you can't just use imrotate because this is homework?
The problem is simple, the answer maybe not : Matlab arrays are indexed from one to N (whereas in many programming langages it's from 0 to (N-1) ).
Try newImg( max( min(1,newX), m1.size() ) , max( min(1,newY), m1.size() ) ) maybe (I don't have Matlab at work so I can tell if it's gonna work), but the resulting image will be croped.
this is an old post so I guess it wont help the OP but as I was helped by his attempt I post here my corrected code.
basically some freedom in the implementation regarding to how you deal with unassigned pixels as well as wether you wish to keep the original size of the pic - which will force you to crop areas falling "outside" of it.
the following function rotates the image around its center, leaves unassigned pixels as "burned" and crops the edges.
function [h] = rot(A,ang)
rotMat = [cos((pi.*ang/180)) sin((pi.*ang/180)); -sin((pi.*ang/180)) cos((pi.*ang/180))];
centerW = round(size(A,1)/2);
centerH = round(size(A,2)/2);
h=255.* uint8(ones(size(A)));
for x = 1:size(A,1)
for y = 1:size(A,2)
point =[x-centerW;y-centerH] ;
product = rotMat * point;
product = int16(product);
newx =product(1,1);
newy=product(2,1);
if newx+centerW<=size(A,1)&& newx+centerW > 0 && newy+centerH<=size(A,2)&& newy+centerH > 0
h(newx+centerW,newy+centerH) = A(x,y);
end
end
end