Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
In MATLAB, when I apply bwlabel on a binary image that has objects, I get a map that identifies which pixels belong to which object. However, there are certain objects that are close to each other that should really belong to the same object. (e.g. Three objects labelled 1,2,3 but they should be merged as one.) Can I merge these regions back somehow?
I have some code to get you started. This is taking a sample image from MATLAB which is full of text. After, what I did was I extracted out the bounding box of each of the letters in the text. Once I did that, I extracted their centroid location, as well as which pixels corresponding to each object within a bounding box. I used these two properties to help me get a rough clustering algorithm going.
Here are the steps to the algorithm I wrote:
Read in the image and extract each object's centroid and pixel locations for each object
Create an array that keeps track of which objects we need to visit
While there is at least one object that we need to visit:
Find any object that has this condition
Assign this object to a new cluster whose membership is just this object so far
Find the distance between this object's centroid to the other objects' centroids
If there are any centroids whose Euclidean distance is below some value, this belongs to the same cluster as the object we have declared in the beginning. Assign all of these objects to the same cluster.
Repeat Step #3 until there are no more clusters we need to visit.
This is the algorithm that I wrote:
clear all;
close all;
%//Read in text and extract properties
BW = imread('text.png');
s = regionprops(BW, 'Centroid', 'PixelList');
%//Create an array that tells us whether or not we have visited this
%//centroid
centroidVisited = false(length(s),1);
%//Create an array that tells us which object belongs to what cluster
membershipList = zeros(length(s),1);
%//List of all centroids for each object
centroidList = reshape([s.Centroid], 2, length(s)).';
%//Initialize cluster count
clusterNumber = 1;
%//Threshold to figure out what is near
distThreshold = 30;
%//Map that gives us which pixel belongs to which cluster
map = zeros(size(BW));
%//If there are any objects we haven't visited...
while (any(centroidVisited == false))
%//Find one object
ind = find(centroidVisited == false, 1);
%//Extract its centroid
cent = s(ind).Centroid;
%//Grab pixels where this object is valid
pixelLocs = s(ind).PixelList;
%//Find Euclidean distance squared between this centroid to all the
%//other centroids
distCentroids = sum(bsxfun(#minus, cent, centroidList).^2, 2);
%//Find those locations that are lower than the centroid
%//Also ensure that we filter out those locations that we have already visited
belowThresh = find(distCentroids < distThreshold*distThreshold & ...
centroidVisited == false);
%//Mark them as visited
centroidVisited(belowThresh) = true;
%//Assign their membership number
membershipList(belowThresh) = clusterNumber;
%//For each object that belongs to this cluster, mark them with this
%//membership number
for k = 1 : length(belowThresh)
placesToMark = s(belowThresh(k)).PixelList;
map(sub2ind(size(BW), placesToMark(:,2), placesToMark(:,1))) = ...
clusterNumber;
end
%//For the next cluster
clusterNumber = clusterNumber + 1;
end
%//Create a colour map that is the same size as the number of clusters
colourMap = jet(clusterNumber);
%//This colour map will contain what letters belong to what cluster (colour
%//coded)
colourMapRed = colourMap(:,1);
colourMapGreen = colourMap(:,2);
colourMapBlue = colourMap(:,3);
mapColumn = map(:) + 1;
redPlane = colourMapRed(mapColumn);
greenPlane = colourMapGreen(mapColumn);
bluePlane = colourMapBlue(mapColumn);
redPlane = reshape(redPlane, size(BW,1), size(BW,2));
greenPlane = reshape(greenPlane, size(BW,1), size(BW,2));
bluePlane = reshape(bluePlane, size(BW,1), size(BW,2));
clusterMapColour = cat(3,redPlane, greenPlane, bluePlane);
figure;
subplot(1,2,1);
imshow(BW);
title('Original Image');
subplot(1,2,2);
imshow(clusterMapColour);
title('Clustered Image');
This is the image that I get:
The variable clusterMapColour will illustrate which objects belong to which cluster in a colour coded map. What you are really after is the map variable. This is pretty much like the output of bwlabel, except that objects with the same label k belong to the cluster k. You will have to play around with the distThreshold variable to get the results that you want. I chose 30 as something arbitrary and something to start with. This means that anything with a 30 pixel radius of a centroid gets classified as the same membership number as that centroid. Also, we keep track of what objects have already been visited so that they don't get reclassified again as we move along in the image.
Good luck!
Related
I am currently trying to determine the area inside specfic contour lines on a Mollweide map projection using Basemap. Specifically, what I'm looking for is the area of various credible intervals in square degrees (or degrees2) of an astronomical event on the celestial sphere. The plot is shown below:
Fortunately, a similar question has already been answered before that helps considerably. The method outlined in the answer is able to account for holes within the contour as well which is a necessity for my use case. My adapted code for this particular method is provided below:
# generate a regular lat/lon grid.
nlats = 300; nlons = 300; delta_lon = 2.*np.pi/(nlons-1); delta_lat = np.pi/(nlats-1)
lats = (0.5*np.pi-delta_lat*np.indices((nlats,nlons))[0,:,:])
lons = (delta_lon*np.indices((nlats,nlons))[1,:,:] - np.pi)
map = Basemap(projection='moll',lon_0=0, celestial=True)
# compute native map projection coordinates of lat/lon grid
x, y = map(lons*180./np.pi, lats*180./np.pi)
areas = []
cred_ints = [0.5,0.9]
for k in range(len(cred_ints)):
cs = map.contourf(x,y,p1,levels=[0.0,cred_ints[k]]) ## p1 is the cumulative distribution across all points in the sky (usually determined via KDE on the data)
##organizing paths and computing individual areas
paths = cs.collections[0].get_paths()
#help(paths[0])
area_of_individual_polygons = []
for p in paths:
sign = 1 ##<-- assures that area of first(outer) polygon will be summed
verts = p.vertices
codes = p.codes
idx = np.where(codes==Path.MOVETO)[0]
vert_segs = np.split(verts,idx)[1:]
code_segs = np.split(codes,idx)[1:]
for code, vert in zip(code_segs,vert_segs):
##computing the area of the polygon
area_of_individual_polygons.append(sign*Polygon(vert[:-1]).area)
sign = -1 ##<-- assures that the other (inner) polygons will be subtracted
##computing total area
total_area = np.sum(area_of_individual_polygons)
print(total_area)
areas.append(total_area)
print(areas)
As far as I can tell this method works beautifully... except for one small wrinkle: this calculates the area using the projected coordinate units. I'm not entirely sure what the units are in this case but they are definitely not degrees2 (the calculated areas are on the order of 1013 units2... maybe the units are pixels?). As alluded to earlier, what I'm looking for is how to calculate the equivalent area in the global coordinate units, i.e. in degrees2.
Is there a way to convert the area calculated in the projected domain back into the global domain in square degrees? Or perhaps is there a way to modify this method so that it determines the area in degrees2 from the get go?
Any help will be greatly appreciated!
For anyone that comes across this question, while I didn't figure out a way to directly convert the projected area back into the global domain, I did develop a new solution by transforming the contour path vertices (but this time defined in the lat/lon coordinate system) via an area preserving sinusoidal projection:
where φ is the latitude, λ is the longitude, and λ0 is the longitude of the central meridian.
This flat projection means you can just use the package Shapely to determine the area of the polygon defined by the projected vertices (in square units for a radius of 1 unit, or more simply steradians). Multiplying this number by (180/π)2 will give you the area in square degrees for the contour in question.
Fortunately, only minor adjustments to the code mentioned in the OP was needed to achieve this. The final code is provided below:
# generate a regular lat/lon grid.
nlats = 300; nlons = 300;
delta_lat = np.pi/(nlats-1); delta_lon = 2.*np.pi/(nlons-1);
lats = (0.5*np.pi-delta_lat*np.indices((nlats,nlons))[0,:,:])
lons = (delta_lon*np.indices((nlats,nlons))[1,:,:])
### FOLLOWING CODE DETERMINES CREDIBLE INTERVAL SKY AREA IN DEG^2 ###
# collect and organize contour data for each credible interval
cred_ints = [0.5,0.9]
ci_areas = []
for k in range(len(cred_ints)):
cs = plt.contourf(lons,lats,p1,levels=[0,cred_ints[k]]) ## p1 is the cumulative distribution across all points in the sky (usually determined via KDE of the dataset in question)
paths = cs.collections[0].get_paths()
##organizing paths and computing individual areas
area_of_individual_polygons = []
for p in paths:
sign = 1 ##<-- assures that area of first(outer) polygon will be summed
vertices = p.vertices
codes = p.codes
idx = np.where(codes==Path.MOVETO)[0]
verts_segs = np.split(vertices,idx)[1:]
for verts in verts_segs:
# transforming the coordinates via an area preserving sinusoidal projection
x = (verts[:,0] - (0)*np.ones_like(verts[:,0]))*np.cos(verts[:,1])
y = verts[:,1]
verts_proj = np.stack((x,y), axis=1)
##computing the area of the polygon
area_of_individual_polygons.append(sign*Polygon(verts_proj[:-1]).area)
sign = -1 ##<-- assures that the other(inner) polygons/holes will be subtracted
##computing total area
total_area = ((180/np.pi)**2)*np.sum(area_of_individual_polygons)
ci_areas.append(total_area)
I would like to return a list of the pixels that belongs to the same region, after clicking on one of them. The input would be the chosen pixel (seed) and the output would be a list of all pixels that have the same value and belongs to the same region (are not separatet by any pixel of different value).
My idea was to create an auxiliary list of seeds and check the neighbours of each of them. If the value of the neighbour is the same as of the seed, it is appended to the region list. My python implementation is below:
def region_growing(x, y):
value = image[x,y]
region = [(x,y),]
seeds = [(x,y),]
while seeds:
seed = seeds.pop()
x = seed[0]
y = seed[1]
for i in range(x-1, x+2):
for j in range(y-1, y+2):
if image[i,j] == value:
point = (i,j,z)
if point not in region:
seeds.append(point)
region.append(point)
return region
It works, but is very slow for bigger regions. What algorithm would you suggest?
The problem is the instruction if point not in region whose execution time will increase with the size of the region. The complexity is thus quadratic.
Another problem is that you visit the same pixels multiple times at the boundary of the region since you only keep track of pixels in the region.
You can avoid this by using a dictionary of visited pixels with the point as key.
def region_growing(x, y):
value = image[x,y]
region = [(x,y),]
seeds = [(x,y),]
visited = {(x,y):true}
while seeds:
seed = seeds.pop()
x = seed[0]
y = seed[1]
for i in range(x-1, x+2):
for j in range(y-1, y+2):
point = (i,j)
if point in visited:
continue
visited[point] = true
if image[i,j] == value:
region.append(point)
seeds.append(point)
return region
Another method is to use a matrix of booleans instead of the dictionary. This is faster but requires more memory space.
I can suggest you to use any region-fill/paint algorithm and patch it not to paint but to track pixels of the same region. The Smith's algorithm is known to be fast and efficient, see Tint Fill Algorithm.
Note that it is inefficient to store all pixels, but as the algorithm suggest horizontal segments are sufficient (thus only two pixels par segment).
I'd like an algorithm to organize a 2D cloud of points in front of a bar graph so that a viewer could easily see the spread of the data. The y location of the point needs to be equal/scaled/proportional to the value of the data, but the x location doesn't matter and would be determined by the algorithm. I imagine a good strategy would be to minimize overlap among the points and center the points.
Here is an example of such a plot without organizing the points:
I generate my bar graphs with points in front of it with MATLAB, but I'm interested just in the best way to choose the x location values of the points.
I have been organizing the points by hand afterwards in Adobe Illustrator, which is time-consuming. Any recommendations? Is this a sub-problem of an already solved problem? What is this kind of plot called?
For high sample sizes, I imagine something like the following would be better than a cloud of points.
I think, mathematically, starting with some array of y-values, it would maximize the sum of the difference between every element from every other element, inversely scaled by the distance between the elements, by rearranging the order of the elements in the array.
Here is the MATLAB code I used to generate the graph:
y = zeros(20,6);
yMean = zeros(1,6);
for i=1:6
y(:,i) = 5 + (8-5).*rand(20,1);
yMean(i) = mean(y(:,i));
end
figure
hold on
bar(yMean,0.5)
for i=1:6
x = linspace(i-0.3,i+0.3,20);
plot(x,y(:,i),'ro')
end
axis([0,7,0,10])
Here is one way that determines x-locations based on grouping into (histogram) bins. The result is similar to e.g. the plot in https://stackoverflow.com/a/1934882/4720018, but retains the original y-values. For convenience the points are sorted, but they could be displayed in order of appearance using the bin_index. Whether this is "the best way" of choosing the x-coordinates depends on what you are trying to achieve.
% Create some dummy data
dummy_data_y = 1+0.1*randn(10,3);
% Create bar plot (assuming you are interested in the mean)
bar_obj = bar(mean(dummy_data_y));
% Obtain data size info
n = size(dummy_data_y, 2);
% Algorithm that creates an x vector for each data column
sorted_data_y = sort(dummy_data_y, 'ascend'); % for convenience
number_of_bins = 5;
for j=1:n
% Get histogram information
[bin_count, ~, bin_index] = histcounts(sorted_data_y(:, j), number_of_bins);
% Create x-location data for current column
xj = [];
for k = 1:number_of_bins
xj = [xj 0:bin_count(k)-1];
end
% Collect x locations per column, scale and translate
sorted_data_x(:, j) = j + (xj-(bin_count(bin_index)-1)/2)'/...
max(bin_count)*bar_obj.BarWidth;
end
% Plot the individual data points
line(sorted_data_x, sorted_data_y, 'linestyle', 'none', 'marker', '.', 'color', 'r')
Whether this is a good way to display your data remains open to discussion.
I was trying to decrease the number of points of a detected edge of an image but I didn't obtain a good result. I want the result to contain exactly 200 pixels of the edges, but those points must be well chosen so the shape remain very clear. How can I do this?
Here's an example image of what I'm working with:
Here are some results that I have received with the code I wrote:
Code written
function y = echantillonnage(x)
contour_image = x;
[lignes,colonnes]=size(x);
n = nombre/200;
contour_image_200px = contour_image;
ok=0;
for i=1:lignes
for j=1:colonnes
if (contour_image_200px(i,j)>0 )
ok=ok+1;
if ( mod(ok,round(n))>0 )
contour_image_200px(i,j)=0;
end
end
end
end
figure,imshow(contour_image_200px);
%résultat
y = contour_image_200px;
end
What you can do is use bwboundaries to trace the boundaries of the objects / edges then sample from those array of points to decrease the number of edge points. The tracing is done in clockwise order, so you are sure that when you subsample from this array, you will get a semblance of order. However, bwboundaries also returns both outer and inner contour boundaries, so you only need a certain amount of traces from the output. We will talk about that later.
bwboundaries works for multiple objects, so all you'd have to do is iterate through each object, sample the edge points and write that to an output result. Note that using bwboundaries doesn't even require edges to be found... as long as the object is clean then it isn't necessary. However, I'm not sure as to the purpose of what you're doing, so let's just operate on the edge detected result.
Let's say we had the following example:
>> A = false(256, 256);
>> A(100:170,100:170) = true;
>> A(3:40,3:40) = true;
>> A(190:220,200:230) = true;
>> imshow(A)
We get this image:
If we performed an edge detection:
>> B = edge(A, 'canny');
>> imshow(B);
We would get this:
Now, if you want to do the subsampling, you would call bwboundaries this way:
[bound,L,N] = bwboundaries(B);
bwboundaries returns a cell array bound of boundaries where each cell is a N x 2 array of spatial coordinates that define the boundary. The first column are the row locations and the second column are the column locations of the boundary points. L is a label matrix that tells you which point each boundary belongs to. We don't need this for your purposes but I might as well talk about it. N is the most important parameter. This defines how many object boundaries there are. This also tells you that the first N cells of bound tells you that those belong to the outer object boundaries.
As such, you can do the following to subsample your edge points and put them into a new matrix, assuming that your edge image is stored in B. Also, you stated that you want to have 200 points per edge. Let's define that parameter as num_edge_points. However, if you have edges that are less than this amount, then I will assume that you'll just want to have all of the edge points selected.
out = false(size(B)); %// Initialize output image
num_edge_points = 200; %// Define number of edge points
%// For each object boundary
for idx = 1 : N
boundary = bound{idx}; %// Get boundary
%// Determine how many points we have
num_pts = size(boundary,1);
%// Generate indices for sampling the boundary
%// If there are less than the minimum, just choose this amount
if num_pts < num_edge_points
ind = 1:num_pts;
else
ind = floor(linspace(1,num_pts,num_edge_points));
end
%// Subsample the edge points
pts = boundary(ind,:);
%// Mark points in output
out(sub2ind(size(B), pts(:,1), pts(:,2))) = true;
end
out will contain your edge image subsampled. To illustrate that we got this right, let's create a new RGB image where we have the edges and the subsampled edge points on top of each other where the subsampled edges are in red:
out_red = 255*uint8(B);
out_greenblue = out_red;
out_greenblue(out) = 0;
out_rgb = cat(3, out_red, out_greenblue, out_greenblue);
imshow(out_rgb);
This is what we get (zoomed-in):
As you can see, the top and bottom rectangles have the full edge points show as there were less than 200 edge points. However, the one in the middle is sparsely sampled as there are more than 200 edge points, but now we are only displaying 200 of them.
If you would like a function to help you facilitate this, you can use the following. I've essentially copied all of the code above and the input is a binary image with edges and the output is a binary image with the subsampled edges:
function [out] = subsample_edge(B)
%// Obtain boundaries for edge image
[bound,L,N] = bwboundaries(B);
out = false(size(B)); %// Initialize output image
num_edge_points = 200; %// Define number of edge points
%// For each object boundary
for idx = 1 : N
boundary = bound{idx}; %// Get boundary
%// Determine how many points we have
num_pts = size(boundary,1);
%// Generate indices for sampling the boundary
%// If there are less than the minimum, just choose this amount
if num_pts < num_edge_points
ind = 1:num_pts;
else
ind = floor(linspace(1,num_pts,num_edge_points));
end
%// Subsample the edge points
pts = boundary(ind,:);
%// Mark points in output
out(sub2ind(size(B), pts(:,1), pts(:,2))) = true;
end
end
If you want to call this function, simply do:
out = subsample_edge(B);
I want to create a program that can count the number of objects in an image. All went smoothly except for the images that have:
objects that have more than 1 color.
overlapping objects.
Following is my program that can only count the number of objects in an image where the object has only 1 color and not overlapped. I use the function bwlabel.
a=imread('Tumpukan Buku2.jpg');
a_citra_keabuan = rgb2gray(a);
threshold = graythresh(a_citra_keabuan);
a_bww = im2bw(a_citra_keabuan,threshold);
a_bw=~a_bww;
[labeled,numObjects]=bwlabel(a_bw);
[m,n]=size(a_bw);
s = regionprops(labeled, 'Centroid');
B = bwboundaries(a_bw);
imshow(a_bw)
hold on
for k = 1:numel(s)
c = s(k).Centroid;
text(c(1), c(2), sprintf('%d', k), ...
'HorizontalAlignment', 'center', ...
'VerticalAlignment', 'middle');
end
for k = 1:length(B)
boundary = B{k};
plot(boundary(:,2), boundary(:,1), 'g', 'LineWidth', 0.2)
end
hold off
Here is the result for an image that has objects in 1 color:
and here is the wrong result for the image with object that has > 1 color and overlapped:
How to solve this problem?
First, you need to clearly define your input data- what types of objects do you want to detect (books, people, any types of objects?), what is the range of environmental conditions (smooth background vs. textured, lighting, perspective).
Then try out various image segmentation techniques and seeing what works for your range of input data. There is no "right" answer - it all depends on your data.
You might also try to incorporate prior information- things that you know when evaluating a scene, that a computer will not know by just evaluating pixels.
For example, maybe all objects are of some minimum size. So your algorithm can filter to only return objects having pixelArea > minArea. Maybe you only expect one objet of each color. So if the color histogram of two detected objects matches to within a given tolerance, consider them to be the same object.