How to calculate weighted (grayscale) centroids of pixel clusters contained within bounding boxes on an image - matlab

I've written some code in MATLAB that converts an image (of stars) into a grayscale image and then into a binary image using a set threshold and then labels each cluster of pixels (stars) that is above this threshold. The labelling produces an output:
e.g.
[1 1 1 0 0 0 0 0 0
1 1 0 0 0 2 2 2 0
0 0 0 3 3 0 2 0 0
0 0 0 3 3 0 0 0 0]
So each cluster of 1's, 2's, 3's etc. represents a star. After this the code then finds the centroids of each pixel cluster and draws a bounding box around each centroid (centered on the centroid) with an 8 x 8 pixel area. The bounding box limits are made by finding xmax, xmin, ymax, ymin of each calculated centroid, which involves either adding or subtracting 4 (pixels) from the x and y coordinates of each centroid.
The weighted centroid is calculated like so:
x_coordinate_centroid = sum(x_coordinate .* pixel_values) / sum_pixel_values
y_coordinate_centroid = sum(y_coordinate .* pixel_values) / sum_pixel_values
x/y_coordinate and pixel values are for the pixels contained within each bounding box.
The bounding box would surround an 8 x 8 pixel area (with the given intensities) on the grayscale image such as:
[100 100 100 90 20 20 0 0
80 90 100 90 20 30 0 0
50 70 100 70 30 0 20 0
50 0 0 60 30 30 0 0
0 0 0 0 0 0 0 0
0 50 0 0 0 0 0 0
0 40 0 0 0 0 0 0
0 20 0 0 0 0 0 0]
The top left value ([xmin, ymax]), for example, could have image coordinates [41, 14] and an intensity of 100.
The output from my code, for example, could give 5 bounding boxes across the grayscale image. I now need to write code that automatically calculates the weighted centroid of each bounding box region. I'm not sure how to go about this, does anyone have any ideas how this can be achieved?
My code for calculating the centroids and their bounding boxes is shown below.
%% Calculate centroids of each labelled pixel cluster within binary image
N = max(B(:)); % total number of pixel labels generated in output array
sum_total = zeros(N,1); % create N x 1 array of 0's
sum_yv = zeros(N,1); % "
sum_xv = zeros(N,1); % "
for xx=1:size(B,2) % search through y positions
for yy=1:size(B,1) % search through x positions
index = B(yy,xx);
if index>0
sum_total(index) = sum_total(index) + 1;
sum_yv(index) = sum_yv(index) + yy;
sum_xv(index) = sum_xv(index) + xx;
end
end
end
centroids = [sum_xv, sum_yv] ./ sum_total; % calculates centroids for each cluster
x_lower_limits = centroids(:,1)-4;
y_lower_limits = centroids(:,2)+4; % lower on image means larger y coord number
x_upper_limits = centroids(:,1)+4;
y_upper_limits = centroids(:,2)-4; % higher on image means lower y coord number
x_lower_limits(x_lower_limits<1)=1; % limit smallest x coord to image axis (1,y)
y_lower_limits(y_lower_limits>size(binary_image,1))=size(binary_image,1); % limit largest y coord to image axis (x,517)
x_upper_limits(x_upper_limits>size(binary_image,2))=size(binary_image,2); % limit largest x coord to image axis (508,y)
y_upper_limits(y_upper_limits<1)=1; % limit smallest y coord to image axis (x,1)
width = x_upper_limits(:,1) - x_lower_limits(:,1); % width of bounding box
height = y_lower_limits(:,1) - y_upper_limits(:,1); % height of bounding box
hold on
for xl=1:size(x_lower_limits,1)
r(xl)=rectangle('Position',[x_lower_limits(xl,1) y_upper_limits(xl,1) width(xl,1) height(xl,1)],'EdgeColor','r');
end
for i=1:size(centroids,1)
plot(centroids(i,1),centroids(i,2),'rx','MarkerSize',10)
end
hold off
%%

Related

Number of black pixels with no black pixel as right neighbour

I'm trying to find the number of black pixels with NO black pixel neighbours at any of right, upper right, lower right .
mask1 = [0|1 0|1 0; 0|1 0|1 0; 0|1 0|1 0];
black = double (x28462847_1_1 < 128);
result = conv2 (black, mask1, 'same');
count = sum (result(:) == 1)
I tried using this mask. Does anyone know where I'm going wrong
To detect all black pixels (ones) which has no black pixels at any of its 3 (upper/lower) right pixels, you can calculate the cross correlation with the following mask:
mask = [0 0 -1;
0 1 -1;
0 0 -1];
The result will be 1 for the desired positions and smaller for the others.
result = conv2 (double(black), rot90(mask,2), 'same') == 1; % Note that I used `conv2` with `rot90` to calculate the cross correlation.
count = sum (result(:)); % count the number of desired elements
Example
For the following image
rng('default')
black = rand(10) > 0.8;
I obtain the following result:
figure
imshow(~black, 'InitialMagnification','fit')
hold on
[x, y] = find(result);
plot(y, x, '*') % highlight the found positions with an asterisk
I think the best way of doing this is:
notblack=black>0; %if its not zero, then its not black
mask=[0 1 0; 1 0 1; 0 1 0]% or mask=[0 0 1; 0 0 1; 0 0 1]; in your case.
result = conv2(double(notblack),mask, 'same'); % sum all values
count=sum(result(:)==sum(mask(:)));

Values do not match data in matlab geographic plot

I am plotting trajectories in Matlab using contourf. I am having an issue with the colors matching the data. I have posted my current image below. All areas that do not have data should be white as they are zero which I specifically specified in the script(they are currently blue-ish which is the the 0.1 to 1 range). In addition, values that are yellow, should be in the blue range(<1). Any suggestions?
Here is the part of my script where I do the plotting:
axesm('mercator', 'MapLatLim', latlim, 'MapLonLim', lonlim,...
'Frame', 'on', 'Grid', 'on', 'MeridianLabel', 'on', 'ParallelLabel', 'on')
setm(gca,'mlabelparallel',-20)
load coastlines
Contours = [0.001 0.01 0.1 1 10 100];
[c,h] = contourfm(latlim, lonlim, u, log(Contours));
colorbar('YTick', log(Contours), 'YTickLabel', Contours);
myColorMap = jet(256).^.3;
myColorMap(1,:) = [1];
colormap(myColorMap)
colorbar
caxis(log([Contours(1) Contours(length(Contours))]));
colorbar('FontSize', 12, 'YTick', log(Contours), 'YTickLabel', Contours);
geoshow(coastlat, coastlon,'Color', 'k')
Contour level, V, in contourfm(lat,lon,Z, V) does not scale your data or colour. It works in a different way than what you thought.
Let's see one example first:
u = rand(8)+0.1; u(1:2,:) = 0; u(5:6,:) = 10; u(7:8,:) = 100;
V = [0,1,40,100];
contourfm([0,1], [0,1], u, V);
mycm = jet(256).^.3; mycm(1,:) = 1;
colormap(mycm)
contourcbar('FontSize', 12, 'YTick', V, 'YTickLabel', V);
where u is
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0.1947 0.6616 0.2413 0.6511 0.4403 0.9112 1.0016 0.5654
0.8422 0.3159 0.5695 0.6478 0.9933 0.1686 0.8387 0.5362
10 10 10 10 10 10 10 10
10 10 10 10 10 10 10 10
100 100 100 100 100 100 100 100
100 100 100 100 100 100 100 100
As you can see, for V = [0,1,40,100] all values from 0 to 1 are white, values from 1 to 40 are cyan and above are red.
Therefore, you must scale your u then assign appropriate contour level. Use contourcbar instead of colorbar to check the colours first.
Apart from the problem with contour level, I suspect the u parameter contains negative values. The colour at the bottom of the colour bar is always assigned to the minimum z value. You must ensure 0 is the minimum value in u, i.e. remove the negative values.

matlab - line equation to 2D matrix with values of 1 and 0

As the title says, I want to covert a line(or any equation) to 2D matrix.
For example: If I have a line equation y = x, then I want it to be like:
0 0 0 0 0 1
0 0 0 0 1 0
0 0 0 1 0 0
0 0 1 0 0 0
0 1 0 0 0 0
1 0 0 0 0 0
and the length of rows and columns can be variable.
Is there a function or method to implement that?
use meshgrid to get x-y grids:
% set resolution parameters
xmin = 1;
xmax = 100;
dx = 0.1;
ymin = 1;
ymax = 100;
dy = 0.1;
% set x-y grid
[xg, yg] = meshgrid(xmin:dx:xmax, ymin:dy:ymax);
% define your function
f = #(x) (x - 30).^2;
% apply it on the x grid and get close y values
D = abs(f(xg) - yg);
bw = D <= 1;
figure;
imshow(bw);
% flip y axis
set(gca,'YDir','normal')
you get:
and then you can further dilate/erode/skeletonize the output
Given x and y coordinates, how to fill in a matrix with those coordinates? As it is said, just do it in a for loop, or use the "sub2ind" function.
% x,y coordinates
x=0:.01:30;
y=10*sin(2*pi*.1*x);
% add offset so that (x,y)-coordinates are always positive
x=x+abs(min(x))+1;
y=y+abs(min(y))+1;
figure,plot(x,y,'.');axis tight
x=ceil(x); y=ceil(y);
im=zeros(max(y),max(x));
ind=sub2ind(size(im),y,x);
im(ind)=1;
figure,imagesc(im),axis image, axis xy;colormap gray;axis tight
xlabel('x'); ylabel('y')
Why not doing it on your own? You loop through all x-coordinates of the matrix that you (probably scaled) use as the x for your function and get an y out, that you (probably scaled) can round and then use as a y coordinate for your matrix to set the 1.

MATLAB calculate area of shape on plot

I Create a plot using imagesc. The X/Y axis are longitude and latitude respectively. The Z values are the intensity of the images for the image shown below. What I'd like to be able to do is calculate the area in each of the polygons shown. Can anybody recommend a straightforward (or any) method in accomplishing this?
EDIT
Forgot to include image.
Below is a toy example. It hinges on the assumption that the Z values are different inside the objects from outside (here: not 0). Also here I assume a straight divider at column 4, but the same principle (applying a mask) can be applied with other boundaries. This also assumes that the values are equidistant along x and y axes, but the question does not state the opposite. If that is not the case, a little more work using bsxfun is needed.
A = [0 2 0 0 0 2 0
3 5 3 0 1 4 0
1 4 0 0 3 2 3
2 3 0 0 0 4 2
0 2 6 0 1 6 1
0 3 0 0 2 3 0
0 0 0 0 0 0 0];
area_per_pix = 0.5; % or whatever
% plot it
cm = parula(10);
cm(1, :) = [1 1 1];
figure(1);
clf
imagesc(A);
colormap(cm);
% divider
dv_idx = 4;
left_object = A(:, 1:(dv_idx-1));
left_mask = left_object > 0; % threshold object
num_pix_left = sum(left_mask(:));
% right object, different method
right_mask = repmat((1:size(A, 2)) > dv_idx, size(A, 1), 1);
right_mask = (A > 0) & right_mask;
num_pix_right = sum(right_mask(:));
fprintf('The left object is %.2f units large, the right one %.2f units.\n', ...
num_pix_left * area_per_pix, num_pix_right * area_per_pix);
This might be helpful: http://se.mathworks.com/matlabcentral/answers/35501-surface-area-from-a-z-matrix
He has not used imagesc, but it's a similar problem.

How can I put margins in an image?

I have a binary image of 18x18 pixels and I want to put margins around this image with the purpose of obtaining an image 20x20 pixels.
The image is binary and it can be represented by a matrix of 1s and 0s. The 0 pixels are in black colour and the 1 pixels are in white colour. I need to put margins of 1 pixel of zeros around the image that I have.
How can I do it?
The padarray function from the image processing toolbox can be used for this purpose:
B=padarray(A,[1,1])
A=ones(18,18);%// your actual image
[M,N] = size(A);
B = zeros(M+2,N+2);%// create matrix
B(2:end-1,2:end-1) = A; %// matrix with zero edge around.
This first gets the size of your image matrix, and creates a zero matrix with two additional columns and rows, after which you can set everything except the outer edges to the image matrix.
Example with a non-square matrix of size [4x6]:
B =
0 0 0 0 0 0 0 0
0 1 1 1 1 1 1 0
0 1 1 1 1 1 1 0
0 1 1 1 1 1 1 0
0 1 1 1 1 1 1 0
0 0 0 0 0 0 0 0
Let's get hackish:
%// Data:
A = magic(3); %// example original image (matrix)
N = 1; %// margin size
%// Add margins:
A(end+N, end+N) = 0; %// "missing" values are implicitly filled with 0
A = A(end:-1:1, end:-1:1); %// now flip the image up-down and left-right ...
A(end+N, end+N) = 0; %// ... do the same for the other half ...
A = A(end:-1:1, end:-1:1); %// ... and flip back
First make a matrix of 20 by 20 zeroes, Zimg, then insert your image matrix into the matrix of zeroes:
Zimg(2:end-1,2:end-1)=img;