I have a polygon which intersects itself multiple times. I try to create a mask from this polygon, i.e., to find all points/pixels location within the polygon. I use the Matlab function poly2mask for this. However, due to the multiple self-intersections this is the results I obtain:
Resulting mask from poly2mask for multi-self-intersecting polygon
So, some areas remain unmasked, because of the intersections. I think Matlab sees this as some sort of inclusions. The Matlab help for poly2mask doesn't mention anything about this. Does anyone have an idea how to also include these regions in the mask?
I obtain good results combining a small erosion/dilation step and imfill as follows:
data = load('polygon_edge.mat');
x = data.polygon_edge(:, 1);
y = data.polygon_edge(:, 2);
bw1 = poly2mask(x,y,ceil(max(y)),ceil(max(x)));
se = strel('sphere',1);
bw2 = imerode(imdilate(bw1,se), se);
bw3 = imfill(bw2, 'holes');
figure
imshow(bw3)
hold on
plot(x(:, 1),y(:, 1),'g','LineWidth',2)
The small erosion and dilation step is needed to be sure that all the regions are connected even at places where the polygon is only connected through a single point, otherwise imfill may see some non-existing holes.
you can use inpolygon:
bw1 = poly2mask(x,y,1000,1000);
subplot(131)
imshow(bw1)
hold on
plot(x([1:end 1]),y([1:end 1]),'g','LineWidth',2)
title('using poly2mask')
[xq,yq] = meshgrid(1:1000);
[IN,ON] = inpolygon(xq,yq,x,y);
bw2 = IN | ON;
subplot(132)
imshow(bw2)
hold on
plot(x([1:end 1]),y([1:end 1]),'g','LineWidth',2)
title('using inpolygon')
% boundary - seggested by another answer
k = boundary(x, y, 1); % 1 == tightest single-region boundary
bw3 = poly2mask(x(k), y(k), 1000, 1000);
subplot(133)
imshow(bw3)
hold on
plot(x([1:end 1]),y([1:end 1]),'g','LineWidth',2)
title('using boundary')
Update - I updated my answer to include boundary - it not seems to work well in my case.
You should first calculate the boundary of your polygon and use this to create your mask.
k = boundary(x, y, 0.99); % 1 == tightest single-region boundary
BW = poly2mask(x(k), y(k), m, n)
Using a shrink factor of 0.99 instead of 1 avoids undercutting, but sharp non-convex corners are still not fitted correctly.
Related
I have a binary image (BW_roi.mat), displayed below. And I wanted to measure the length of every line segment.
I tried the solution given in this link hough-transform. But it does not work out for me. It just measured the length of some lines as shown below.
I tried the other code
clc; clear all; close all;
load BW_ROI.mat
boundaries = bwboundaries(BW_roi_nodot);
patchno=1 %Let select patch 1
b = boundaries{patchno}; % Extract N by 2 matrix of (x,y) locations.
x = b(:, 1);
y = b(:, 2);
It though gives me points (x,y) that make up these polygons. But how can I get the line segment length of specific patch?
I'd propose using convexhull in combination with the algorithm here to reduce the number of vertices in the detected polygons. Seems to work pretty well. I'll leave it to you to use the adjacency matrix produced by bwboundaries to throw away duplicates.
load BW_ROI.mat
[B,L,N,A] = bwboundaries(BW_roi_nodot);
for k = 1:N
boundary = fliplr(B{k});
% take a convex hull of the bounded polygon
idxn = convhull(boundary, 'Simplify', true);
% use this algorithm to further simplify the polygon
% https://ww2.mathworks.cn/matlabcentral/fileexchange/41986-ramer-douglas-peucker-algorithm
point_reduced = DouglasPeucker(boundary(idxn,:), 5);
% make the shape, get the side lengths
shp = polyshape(point_reduced);
vertex_deltas = diff([shp.Vertices; shp.Vertices(1,:)], 1);
edge_lengths = sqrt(sum(vertex_deltas.^2, 2));
% plot animation
imshow(BW_roi_nodot), hold on
plot(shp), drawnow, pause(0.5)
end
I need to create a binary mask from a series of coordinates. My current code is shown below but the edges of the resulting image are not smooth. I think it is not precise and I need to make sure I am connecting exact coordinate and connect them together.
Here on the left side, I plotted the points (always 42 points) and on the right side is the output of the code. As you can see the edges are not smooth.
Here is the current code and the output: (coordinates are attached)
im is an image of size 112 x 112, filled with zero everywhere except the X, Y coordinates and inside the region filled with the 255.
function BW = mask_data(X,Y, im)
X = round(X);
Y = round(Y);
%round coordinates
X ( X < 1 ) = 1;
Y ( Y < 1 ) = 1;
BW = im > 255;
for p = 1:length(X)
BW(Y(p),X(p)) = 1;
end
BW = BW * 255;
BW = bwconvhull(BW);
BW = im2uint8(BW);
figure;
imshow(BW);
close all;
end
I believe the union convex hull is still your best bet. If you have images that are actually comprised of a single object then your shown algorithm should work just fine, though you are doing some redundant steps in your shown code. If that is not the case, then you may want to consider finding the convex hull of multiple components through adding the objects option to your bwconvhull call. If you strongly believe that the results are "not precise" then you may want to show an example image in which the algorithm actually fails.
As per the results not being smooth, you should logically not expect smooth boundaries for an image of size 112 x 112 with an object boundary similar to what you have shown. However, I would simply smooth the results if smooth images are preferred:
originalImage = imread('Adrress\to\your\image.png');
% To have the same image size as yours
originalImage = imresize(originalImage, [112 112]);
% Create a binary image
binaryImage = im2bw(originalImage);
% Create a binary convex hull image
UnionCH = bwconvhull(binaryImage);
% Smooth the results (note the change of binary class)
% Second arg (0.7) is the std dev of the Gaussian smoothing kernel
SmoothUnionCH = imgaussfilt(single(UnionCH), 0.7);
figure
subplot(131)
imshow(binaryImage)
title('Binary Image')
subplot(132)
imshow(UnionCH)
title('Binary Convex Hull Image')
subplot(133)
imshow(SmoothUnionCH,[])
title('Smooth Convex Hull Image')
You can adjust the size of the smoothing kernel of course. The results for the code above:
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') ;
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.
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: