How to find all edge pixels around one region in binary image? - matlab

The region's hole has been filled and I want to find all edge pixels in anti-clockwise direction.
At first, my solution is
1、 find all pixel at edge
2、get x-direction between centroid and the pixel.
3、sort it.
How to improve it?
Image and code as follows, but when I test code, it found that the concave and bulge make it useless because for at these angles there are possible many points.
function nAngle = create_angle_array(nPosition,centroid)
a = repmat(centroid,[size(nPosition,1),1])
nAngle = mod(angle((nPosition-a)*[1;1j]),2*pi)
end
se = strel('rect',[3,3]);
erodeImage = imerode(binaryImage,se);
erodeImageLeft = binaryImage - erodeImage;
% countPixel = sum(erodeImageLeft(:)== true);
[edgeRow_a,edgeCol_a] = find(erodeImageLeft);
if isscalar(edgeRow_a)
edgeRow = [edgeRow_a];
else
edgeRow = edgeRow_a;
end
if isscalar(edgeCol_a)
edgeCol = [edgeCol_a];
else
edgeCol = edgeCol_a;
end
disp(edgeRow);
disp(edgeCol);
angleValue = create_angle_array(cat(2,edgeRow,edgeCol),centroid);
disp(angleValue);
nPixel = cat(2,angleValue,edgeRow,edgeCol);
fprintf('size(nPixelA) is [%s]\n', int2str(size(nPixel)));
disp(nPixel)
nEdgePixel = sortrows(nPixel)

As you've seen, sorting the pixels along an axis does not guarantee a consistent ordering. A more reliable way is to take a pixel and find the next pixel adjacent to it in the proper direction.
To do this, we need to search the adjacent pixels in the proper order. For a counter-clockwise ordering, we'll define the directions as:
1 0 7
\ | /
\|/
2--+--6
/|\
/ | \
3 4 5
For the current pixel, we will start searching for the next pixel in the direction of the previous pixel plus 1 (modulo 8). So if the previous pixel was in direction 6, we'll first look in direction 7, then 0, then 1, until we find the next perimeter pixel. Then repeat until we reach the starting pixel again.
When we select the starting pixel, we don't have a "previous" pixel, so the easiest way to ensure that we don't miss any pixels is to start on one of the extremes (say, leftmost). Then set the direction of the "previous" pixel to the direction you know there are no more pixels, that is, to the left or direction 2. So,
Set current_pixel and assign previous_direction as above. Add current_pixel to perimeter_list.
Repeat:
    Starting at previous_direction + 1 (modulo 8), search adjacent pixels in order until you find another perimeter pixel.
    If the new pixel is equal to the starting pixel, break.
    Add new perimeter pixel to list. If the direction the new pixel was found in is d, set previous_direction to d+4 mod 8. Set the current_pixel to the newly-found pixel.
This will find all of the perimeter pixels of a region in the proper order without having to explicitly find the perimeter pixels first. Any "spikes", or one pixel wide lines, protruding from the region will list the pixels twice, once traversing the line in each direction, so your final list might be longer than the number of perimeter pixels. You can also skip filling the holes, unless you need them filled for a future step.
Some things to look out for are making sure that you don't look outside the bounds of the image, and getting MATLAB's 1-based array indexing to work with mod. Personally, I have a mod1 function to do one-based modulo. If you do this, just change the direction numbers to be 1 to 8 instead of 0 to 7. I chose to start with the leftmost pixel because that's what find(bwimg, 1) is going to return.

Related

How to turn a pair of X, Y points in decimal into an image more accurately?

Normally this table is around 600 points but I didn't want to type it all, let's say I have points like this:
240.021000000000 291.414100000000
250.985100000000 297.566300000000
260.143500000000 310.125800000000
270.605100000000 315.355400000000
279.775500000000 327.352000000000
288.302300000000 335.765900000000
301.487400000000 348.374900000000
313.892100000000 340.501400000000
323.391400000000 328.044800000000
334.615100000000 322.182400000000
Where number on the left is X and number on the right is Y of a coordinate where there is a "thing" or let's say where the color is white and rest is black.
And I want to turn this into an image, what I did so far is this:
% Added 50 more pixels to not stick to edge of image
image = uint8(zeros(max(table(:, 1))+50, max(table(:, 2)+50))
for i = size(table(:))
image(round(table(i, 1)), round(table(i, 2))) = 256;
end
imshow(image);
I am wondering how accurate this is and how I can improve it or if I can improve it?
Reason here is I will do this for two tables and need to compare the similarity of two images that belong to these tables, but I don't even have an image and rounding didn't feel like the best way since 270.49999999 and 270.5000001 are similar, yet 270 and 271 are different. There can also be points that overlap each other if all is just rounded up or just rounded down.
I see two approaches, you can increase the resolution by binning your image to be N*600 by N*600 point instead of 600x600, for example 6000x6000, then each 0.1 value will be in a different pixel. Or, you can convolve your 1 pixel with a distribution like a 5x5 Gaussian of signa=1 that will capture the spread around that point position. For example using exp(- ((x-xn).^2+(x-yn).^2)/2) where xn and yn are the n-th point coordinate in your question, and x and y are obtained via [x y]=meshgrid(1:600) or whatever your image size is....

calculating the scores using matlab

I am working on calculating the scores for air rifle paper target. I'm able to calculate the distance from center of the image to the center of the bullet hole in Pixels.
Here's my code:
I = imread('Sample.jpg');
RGB = imresize(I,0.9);
imshow(RGB);
bw = im2bw(RGB,graythresh(getimage));
figure, imshow(bw);
bw2 = imfill(bw,'holes');
s = regionprops(bw2,'centroid');
centroids = cat(1,s.Centroid);
dist_from_center = norm(size(bw(:,:,1))/2 - centroids,2);
hold(imgca,'on');
plot(imgca,centroids(:,1),centroids(:,2),'r*');
hold(imgca,'off');
numberOfPixels = numel(I);
Number_Of_Pixel = numel(RGB);
This is the raw image with one bullet hole.
This is the result I am having.
This is the paper target I'm using to get the score.
Can any one suggest me how to calculate the score using this.
See my walk through your problem in Python
It's a very fun problem you have.
I assumed you have already a way of getting the binary holes mask (since you gave us the image)
Some scores are wrong because of target centering issues in given image
Given hole-mask, find 2D shot center
I assume that the actual images would include several holes instead of one.
Shot locations extracted by computing the local maxima of the distance transform of the binary hole image. Since the distance transform gives as intensity output the distance from the examined point to a border, this allows us to compute the centermost pixels as local maximum.
Local maximum technique I used is computing a maximum filter of your image with a given size (10 for me) and find the pixels that have filtered == original.
You have to remove the 0-valued "maxima" but apart from that it's a nice trick to remember, since it works in N-dimension by using a N-dimensional maximum filter.
Given a 2D position of shot center, compute the score
You need to transform your coordinate system from cartesian (X,Y) to polar (distance,angle).
Image from MathWorks to illustrate the math.
To use the center of image as reference point, offset each position by the image center vector.
Discarding the angle, your score is directly linked to the distance from center.
Your score is an integer that you need to compute based on distance :
As I understand you score 10 if you are at distance 0 and decrease till 0 points.
This means the scoring function is
border_space = 10 px # distance between each circle, up to you to find it :)
score = 10 - (distance / border_space) # integer division though
with the added constraint that score can not be negative :
score = max(10 - (distance / border_space),0)
Really do look through my ipython notebook, it's very visual
Edit: Regarding the distance conversion.
Your target practice image is in pixels, but these pixel distances can be mapped to millimeters : You probably know what your target's size is in centimeters (it's regulation size, right ?), so you can set up a conversion rate:
target_size_mm = 1000 # 1 meter = 1000 millimeters
target_size_px = 600 # to be measured for each image
px_to_mm_ratio = target_size_mm / target_size_px
object_size_px = 102 # any value you want to measure really
object_size_mm = object_size_px * px_to_mm_ratio
Everytime you're thinking about a facet of your problem, think "Is what I'm looking at in pixels or in millimeters ?". Try to conceptually separate the code that uses pixels from the one in millimeters.
It is coding best practice to avoid these assumptions where you can, so that if you get a bunch of images from different cameras, with different properties, you can "convert" everything to a common format (millimeters) and have a uniform treatment of data afterwards

How to determine Boundary Cut Utilization MATLAB?

Working on 2D Rectangular Nesting. Need to find the utilization percentage of the material. Assuming i have the length, breadth, left-bottom position of each rectangle. What is the best way to determine the Boundary-Cut Utilization?
Objective:- To find the AREA under the RED Line.
Sample images attached to depict what i have done and what i need.
What i have done
what i need
Another Example image of rectangles packed with allowance
If you're interested in determining the total "area" underneath the red line, one suggestion I have is if you have access to the Image Processing Toolbox, simply create a binary image where we draw all of the rectangles on the image at once, fill all of the holes, then to determine the area, just determine the total sum of all of the binary "pixels" in the image. You said you have the (x,y) positions of the bottom-left corner of each rectangle, as well as the width and height of each rectangle. To make this compatible in an image context, the y axis is usually flipped so that the top-left corner of the space is the origin instead of the bottom-left. However, this shouldn't affect our analysis as we are simply reflecting the whole 2D space downwards.
Therefore, I would start with a blank image that is the same size as the grid you are dealing with, then writing a loop that simply sets a rectangular grid of coordinates to true for each rectangle you have. After, use imfill to fill in any of the holes in the image, then calculate the total sum of the pixels to get the area. The definition of a hole in an image processing context is any black pixels that are completely surrounded by white pixels. Therefore, should we have gaps that are surrounded by white pixels, these will get filled in with white.
Therefore, assuming that we have four separate variables of x, y, width and height that are N elements long, where N is the number of rectangles you have, do something like this:
N = numel(x); %// Determine total number of rectangles
rows = 100; cols = 200; %// Define dimensions of grid here
im = false(rows, cols); %// Declare blank image
%// For each rectangle we have...
for idx = 1 : N
%// Set interior of rectangle at location all to true
im(y(idx)+1:y(idx)+height(idx), x(idx)+1:x(idx)+width(idx)) = true;
end
%// Fill in the holes
im_filled = imfill(im, 'holes');
%// Determine total area
ar = sum(im_filled(:));
The indexing in the for loop:
im(y(idx)+1:y(idx)+height(idx), x(idx)+1:x(idx)+width(idx)) = true;
Is a bit tricky to deal with. Bear in mind that I'm assuming that y accesses the rows of the image and x accesses the columns. I'm also assuming that x and y are 0-based, so the origin is at (0,0). Because we access arrays and matrices in MATLAB starting at 1, we need to offset the coordinates by 1. Now, the beginning index for the row starts from y(idx)+1. We end at y(idx) + height(idx) because we technically start at y(idx)+1 but then we need to go up to height(idx) but then we also subtract by 1 as your coordinates begin at 0. Take for example a line with the width of 20, from x = 0 to x = 19. This width is 20, but we draw from 0, up to 20-1 which is 19. Because of the indexing starting at 1 for MATLAB, and the subtraction of 1 due to the 0 indexing, the +1 and -1 cancel, which is why we are just left with y(idx) + height(idx). The same can be said with the x coordinate and the width.
Once we draw all of the rectangles in the image, we use imfill to fill up the holes, then we can sum up the total area by just unrolling the whole image into a single vector and invoking sum. This should (hopefully) get what you need.
Now, if you want to find the area without the filled in holes (I suspect this is what you actually need), then you can skip the imfill step. Simply apply the sum on the im, instead of im_filled, and so:
ar = sum(im(:));
This will sum up all of the "white" pixels in the image, which is effectively the area. I'm not sure what you're actually after, so use one or the other depending on your needs.
Boundary-Cut Area without using Image Processing Toolbox.
The Detailed question description and answer could be found here
This solution is applicable only to Rectangular parts.

find the number of neighbour pixels

I create an image, which has random groups of random pixels:
img=ones(100,100)
numRandom = 505;
linearIndices = ceil(numel(img) * rand(1, numRandom));
img(linearIndices) = 0;
imshow(img)`
Then I turn this image into binary and find the area of each group of pixels with:
regionprops(L, 'Area');
I also need the perimeter of each group. Unfortunately, regionprops doesn't give me correct results (for example, if there is one pixel the function returns 0 instead of 4), so I think that it is better to find number of neighbour pixels of each group (so that for the case of only one pixel the answer will be 4). If the group is on the border of the image it should also be taken into consideration.
Can anybody give me a tip about how to do it?
Perimeter and regionprops is not what you need then, or find all these single pixels using regionprops(L, 'Area')==1 and set their perimeter to 4....
From Matlab documentation:
Perimeter — is the distance around the boundary of the region. regionprops computes the perimeter by calculating the distance between each adjoining pair of pixels around the border of the region. If the image contains discontiguous regions, regionprops returns unexpected results. The following figure shows the pixels included in the perimeter calculation for this object.
From this image you can see that the edge pixels are counted only once, not twice.

MATLAB: Return array of values between two co-ordinates in a large matrix (diagonally)

If I explain why, this might make more sense
I have a logical matrix (103x3488) output of a photo of a measuring staff having been run through edge detect (1=edge, 0=noedge). Aim- to calculate the distance in pixels between the graduations on the staff. Problem, staff sags in the middle.
Idea: User inputs co-ordinates (using ginput or something) of each end of staff and the midpoint of the sag, then if the edges between these points can be extracted into arrays I can easily find the locations of the edges.
Any way of extracting an array from a matrix in this manner?
Also open to other ideas, only been using matlab for a month, so most functions are unknown to me.
edit:
Link to image
It shows a small area of the matrix, so in this example 1 and 2 are the points I want to sample between, and I'd want to return the points that occur along the red line.
Cheers
Try this
dat=imread('83zlP.png');
figure(1)
pcolor(double(dat))
shading flat
axis equal
% get the line ends
gi=floor(ginput(2))
x=gi(:,1);
y=gi(:,2);
xl=min(x):max(x); % line pixel x coords
yl=floor(interp1(x,y,xl)); % line pixel y coords
pdat=nan(length(xl),1);
for i=1:length(xl)
pdat(i)=dat(yl(i),xl(i));
end
figure(2)
plot(1:length(xl),pdat)
peaks=find(pdat>40); % threshhold for peak detection
bigpeak=peaks(diff(peaks)>10); % threshold for selecting only edge of peak
hold all
plot(xl(bigpeak),pdat(bigpeak),'x')
meanspacex=mean(diff(xl(bigpeak)));
meanspacey=mean(diff(yl(bigpeak)));
meanspace=sqrt(meanspacex^2+meanspacey^2);
The matrix pdat gives the pixels along the line you have selected. The meanspace is edge spacing in pixel units. The thresholds might need fiddling with, depending on the image.
After seeing the image, I'm not sure where the "sagging" you're referring to is taking place. The image is rotated, but you can fix that using imrotate. The degree to which it needs to be rotated should be easy enough; just input the coordinates A and B and use the inverse tangent to find the angle offset from 0 degrees.
Regarding the points, once it's aligned straight, all you need to do is specify a row in the image matrix (it would be a 1 x 3448 vector) and use find to get non-zero vector indexes. As the rotate function may have interpolated the pixels somewhat, you may get more than one index per "line", but they'll be identifiable as being consecutive numbers, and you can just average them to get an approximate value.