I have a binary image:
and I'm trying to see if a 100x150 rectangle will fit in any blank space on the map.
I tried creating a rectangular strel & then eroding & dilating the picture to get rid of any areas smaller than needed:
se = strel('rectangle',[150, 100]);
BW = imerode(BW,se);
BW = imdilate(BW,se);
Unfortunately, it finds a hole prematurely
which is only 80x150. I think the erosion is failing since it's against the wall & only needs half the width, but don't know how to fix it.
Also, if I'm headed down the wrong path, please feel free to set me straight. Ultimately, I just need to find the upper-left corner of the a blank space at least as big as 100x150.
The approach below works quite well and runs fairly quickly. It has a few nested loops but you can probably further optimize the performance, I mainly just wanted to get it working for you. Keep in mind though that if you comment out the fprintf() and the plotting command, that will speed things up.
I downloaded your image from your Stack post but I believe that my downloaded version has a different size (398x398) than the raw data you are working with, so keep that in mind when viewing my results below.
As indicated in the code, you supply the width (w) and height (h), the algorithm then returns all of the (col, row) positions where the rectangle can fit.
Side Note: I believe this provides a solution to a 2D version of the Bin packing problem, but I'm not sure about that, you can check out the link above if thats of interest to you.
Either way, its a great example of computational problem where an exhaustive search can be a carried out rather quickly.
To verify the results, I added simple plotting of the rectangles. Keep in mind that if the rectangle fits in more than one position, a plot of the multiple rectangles begins to look rather jumbled as they are drawn repeatedly on top of one another (with offsets).
As an example case where only a single rectangle is found, I use: w = 29; h = 102; and then the result shows that the only position where this particular rectangle can fit, has the upper left corner = (row = 295, col = 368) (this rectangle size will likely only work for my downloaded version of your data):
In summary, I first I load the data and then convert to a binary map (0's and 1's):
% Note: '0' = black; '1' = white
data = round(im2double(rgb2gray(imread(filepath))));
figure(1);imshow(data); set(gcf,'Color',[1 1 1]);
hold on;
Input the search width and height:
w = 29;
h = 102;
sze = size(data);
numRows = sze(1);
numCols = sze(2);
Next we just do a search to see what will fit at each row and col position:
for col = 1:numCols - w - 1
for row = 1:numRows - h - 1
doesFit = fitshere(data, row,col, w, h);
if (doesFit == 1)
fprintf('row = %d; col = %d \n',row,col);
colX = [col col+w col+w col col];
colY = [row row row+h row+h row];
line(colX,colY,'Color','r','linewidth',2);
end
end
end
hold off;
You will need the following function to check if a given rectangle can fit in the array:
function [val] = fitshere(data, row, col, w, h)
val = 1;
for i = col:col + w
for j = row:row + h
if (data(j,i) == 0) % if this is true, we are in the black!
val = 0;
return;
end
end
end
return;
If your interested in knowing if your rectangle will fit at all (say either width X height or height X width), you can simply repeat the search after swapping the width and height.
Hope this helps.
lets do this with some matlab idioms
M=binaryImage;
sz=size(M);
nrows = 100;
ncols = 150 ;
colsum = cumsum(M,1);
cols_are_good = colsum(nrows+1:end,:)-colsum(1:end-nrows+1,:)==0;
% nrows empty rows below this point. in this column
rows_are_also_good = cols_are_good(:,ncols+1:end)-cols_are_good(:,1:end+1-ncols)==0;
and Bob's your uncle, that last variable contains 1 in all places that have nrows below them clear and each of those has ncols to the side
Related
I have a black and white image as shown below:
I want to separate the white components of this image and then save them as separate image. This image has four white parts. I want to separate them and save four new images; each containing a white part of the image.
To achieve this, I wrote the following code:
BW=imread('img11_Inp.jpg');
imshow(BW);
BW=imbinarize(BW);
[L, num] = bwlabel(BW);
for k = 1 : num
thisBlob = ismember(L, k);
h = int2str(k);
filname = strcat(h,'_Out.jpg');
imwrite(thisBlob,filname);
figure
imshow(thisBlob, []);
end
Problem
This code separates the white parts and saves them but the size of the white part saved in the new image is same as in the original image. See the output images below:
Output images
Desired Output images
I want the output images to contain increased size of the white part of the original image. Following images are the ones I want:
Question
How can I modify the above code so that I can get the desired output images ?
Steps:
Find the boundary of the white portion.
To include the black portion, subtract a constant from the top left corner.If it is less than or equal to zero, it means we have reached or exceeded the left corner of the actual image, so set it 1. If it is greater than zero then all is fine.
Make similar adjustments for the right bottom corner.
Crop to the desired size.
Code:
%Finding the boundary of the white
[~, c1] = find(thisBlob, 1); [~, r1] = find(thisBlob.', 1);
[~, c2] = find(thisBlob, 1, 'last'); [~, r2] = find(thisBlob.', 1, 'last');
%Making adjustments to include the black portion
k = 10; %constant defining max number of black pixels
mxlim = size(X); %to be used to confirm that we don't exceed the boundary of the image
r1 = r1-10; r1(r1<=0)=1; c1 = c1-10; c1(c1<=0)=1;
r2 = r2+10; r2(r2>mxlims(1)) = mxlim(1); c2 = c2+10; c2(c2>mxlim(2)) = mxlims(2);
%Extracting the desired portion
thisBlob = thisBlob(r1:r2, c1:c2);
Output for the provided images:
You can change the number of black pixels by changing the constant k in the code.
Test Case when the white portion is on the Edge:
To verify if it also works if the white portion is on the edge like this image:
The code gives the following output for the above image:
Actually, what you want to perform is a crop with a little bit of span around the object. This can be easily achieved using imcrop that you must call providing the rectangle you want to keep.
In order to identify the rectangle:
Find the minimum an maximum rows that contain a white pixel (y-axis);
Find the minimum an maximum columns that contain a white pixel (x-axis);
Calculate width and height of the rectangle using maximum - minimum.
Since you want to crop using a little margin (in my example I set its value to 10 but you have full control over it), you must subtract that margin to minimum values and adding it to maximum values, but paying attention not to go out of the boundaries of the image (that's where the little min-max game comes into play).
Here is the full working code:
img = imread('img11_Inp.jpg');
imshow(img);
img_bin = imbinarize(img);
[lab,num] = bwlabel(img_bin);
span = 10;
for k = 1:num
file = [num2str(k) '_Out.jpg'];
blob = ismember(lab,k);
blob_size = size(blob);
col_idx = find(any(blob == true,1));
x1 = max([1 (min(col_idx) - span)]);
x2 = min([blob_size(2) (max(col_idx) + span)]);
width = x2 - x1;
row_idx = find(any(blob == true,2));
y1 = max([1 (min(row_idx) - span)]);
y2 = min([blob_size(1) (max(row_idx) + span)]);
height = y2 - y1;
blob_crop = imcrop(blob,[x1 y1 width height]);
imwrite(blob_crop,file);
figure();
imshow(blob_crop,[]);
end
Also, don't use int2str(k) in order to obtain a string representation of your index. Your index is actually a double so you are forcing a double (no pun intended) cast: double -> int and then int -> char array. Just use num2str.
Result:
I have an image (white background with 1-5 black dots) that is called main.jpg (main image).
I am trying to place another image (secondary.jpg) in every black dot that is found in main image.
In order to do that:
I found the black pixels in main image
resize the secondary image to specific size that I want
plot the image in every coordinate that I found in step one. (the black pixel should be the center coordinates of the secondary image)
Unfortunately, I don't know how to do the third step.
for example:
main image is:
secondary image is:
output:
(The dots are behind the chairs. They are the image center points)
This is my code:
mainImage=imread('main.jpg')
secondaryImage=imread('secondary.jpg')
secondaryImageResized = resizeImage(secondaryImage)
[m n]=size(mainImage)
for i=1:n
for j=1:m
% if it's black pixel
if (mainImage(i,j)==1)
outputImage = plotImageInCoordinates(secondaryImageResized, i, j)
% save this image
imwrite(outputImage,map,'clown.bmp')
end
end
end
% resize the image to (250,350) width, height
function [ Image ] = resizeImage(img)
image = imresize(img, [250 350]);
end
function [outputImage] = plotImageInCoordinates(image, x, y)
% Do something
end
Any help appreciated!
Here's an alternative without convolution. One intricacy that you must take into account is that if you want to place each image at the centre of each dot, you must determine where the top left corner is and index into your output image so that you draw the desired object from the top left corner to the bottom right corner. You can do this by taking each black dot location and subtracting by half the width horizontally and half the height vertically.
Now onto your actual problem. It's much more efficient if you loop through the set of points that are black, not the entire image. You can do this by using the find command to determine the row and column locations that are 0. Once you do this, loop through each pair of row and column coordinates, do the subtraction of the coordinates and then place it on the output image.
I will impose an additional requirement where the objects may overlap. To accommodate for this, I will accumulate pixels, then find the average of the non-zero locations.
Your code modified to accommodate for this is as follows. Take note that because you are using JPEG compression, you will have compression artifacts so regions that are 0 may not necessarily be 0. I will threshold with an intensity of 128 to ensure that zero regions are actually zero. You will also have the situation where objects may go outside the boundaries of the image. Therefore to accommodate for this, pad the image sufficiently with twice of half the width horizontally and twice of half the height vertically then crop it after you're done placing the objects.
mainImage=imread('https://i.stack.imgur.com/gbhWJ.png');
secondaryImage=imread('https://i.stack.imgur.com/P0meM.png');
secondaryImageResized = imresize(secondaryImage, [250 300]);
% Find half height and width
rows = size(secondaryImageResized, 1);
cols = size(secondaryImageResized, 2);
halfHeight = floor(rows / 2);
halfWidth = floor(cols / 2);
% Create a padded image that contains our main image. Pad with white
% pixels.
rowsMain = size(mainImage, 1);
colsMain = size(mainImage, 2);
outputImage = 255*ones([2*halfHeight + rowsMain, 2*halfWidth + colsMain, size(mainImage, 3)], class(mainImage));
outputImage(halfHeight + 1 : halfHeight + rowsMain, ...
halfWidth + 1 : halfWidth + colsMain, :) = mainImage;
% Find a mask of the black pixels
mask = outputImage(:,:,1) < 128;
% Obtain black pixel locations
[row, col] = find(mask);
% Reset the output image so that they're all zeros now. We use this
% to output our final image. Also cast to ensure accumulation is proper.
outputImage(:) = 0;
outputImage = double(outputImage);
% Keeps track of how many times each pixel was hit by the object
% This is so that we can find the average at each location.
counts = zeros([size(mask), size(mainImage, 3)]);
% For each row and column location in the image
for i = 1 : numel(row)
% Get the row and column locations
r = row(i); c = col(i);
% Offset to get the top left corner
r = r - halfHeight;
c = c - halfWidth;
% Place onto final image
outputImage(r:r+rows-1, c:c+cols-1, :) = outputImage(r:r+rows-1, c:c+cols-1, :) + double(secondaryImageResized);
% Accumulate the counts
counts(r:r+rows-1,c:c+cols-1,:) = counts(r:r+rows-1,c:c+cols-1,:) + 1;
end
% Find average - Any values that were not hit, change to white
outputImage = outputImage ./ counts;
outputImage(counts == 0) = 255;
outputImage = uint8(outputImage);
% Now crop and show
outputImage = outputImage(halfHeight + 1 : halfHeight + rowsMain, ...
halfWidth + 1 : halfWidth + colsMain, :);
close all; imshow(outputImage);
% Write the final output
imwrite(outputImage, 'finalimage.jpg', 'Quality', 100);
We get:
Edit
I wasn't told that your images had transparency. Therefore what you need to do is use imread but ensure that you read in the alpha channel. We then check to see if one exists and if one does, we will ensure that the background of any values with no transparency are set to white. You can do that with the following code. Ensure this gets placed at the very top of your code, replacing the images being loaded in:
mainImage=imread('https://i.stack.imgur.com/gbhWJ.png');
% Change - to accommodate for transparency
[secondaryImage, ~, alpha] = imread('https://i.imgur.com/qYJSzEZ.png');
if ~isempty(alpha)
m = alpha == 0;
for i = 1 : size(secondaryImage,3)
m2 = secondaryImage(:,:,i);
m2(m) = 255;
secondaryImage(:,:,i) = m2;
end
end
secondaryImageResized = imresize(secondaryImage, [250 300]);
% Rest of your code follows...
% ...
The code above has been modified to read in the basketball image. The rest of the code remains the same and we thus get:
You can use convolution to achieve the desired effect. This will place a copy of im everywhere there is a black dot in imz.
% load secondary image
im = double(imread('secondary.jpg'))/255.0;
% create some artificial image with black indicators
imz = ones(500,500,3);
imz(50,50,:) = 0;
imz(400,200,:) = 0;
imz(200,400,:) = 0;
% create output image
imout = zeros(size(imz));
imout(:,:,1) = conv2(1-imz(:,:,1),1-im(:,:,1),'same');
imout(:,:,2) = conv2(1-imz(:,:,2),1-im(:,:,2),'same');
imout(:,:,3) = conv2(1-imz(:,:,3),1-im(:,:,3),'same');
imout = 1-imout;
% output
imshow(imout);
Also, you probably want to avoid saving main.jpg as a .jpg since it results in lossy compression and will likely cause issues with any method that relies on exact pixel values. I would recommend using .png which is lossless and will also likely compress better than .jpg for synthetic images where the same colors repeat many times.
I am performing a whisker-tracking experiments. I have high-speed videos (500fps) of rats whisking against objects. In each such video I tracked the shape of the rat's snout and whiskers. Since tracking is noisy, the number of whiskers in each frame may be different (see 2 consecutive frames in attached image, notice the yellow false-positive whisker appearing in the left frame but not the right one).
See example 1:
As an end result of tracking, I get, for each frame, a varying number of variable-length vectors; each vector corresponding to a single whisker. At this point I would like to match the whiskers between frames. I have tried using Matlab's sample align to do this, but it works only somewhat properly. Its results are attached below (in attached image showing basepoint of all whiskers over 227 frames).
See example 2:
I would like to run some algorithm to cluster the whiskers correctly, such that each whisker is recognized as itself and separated from other over the course of many frames. In other words, I would like each slightly sinusoidal trajectory in the second image to be recognized as one trajectory. Whatever sorting algorithm I use should take into account that whiskers may disappear and reappear between consecutive frames. Unfortunately, I'm all out of ideas...
Any help?
Once again, keep in mind that for each point in attached image 2, I have many data points, since this is only a plot of whisker basepoint, while in actuality I have data for the entire whisker length.
This is how I would deal with the problem. Assuming that data vectors of different size are in a cell type called dataVectors, and knowing the number of whiskers (nSignals), I would try to extend the data to a second dimension derived from the original data and then perform k-means on two dimensions.
So, first I would get the maximum size of the vectors in order to convert the data to a matrix and do NaN-padding.
maxSize = -Inf;
for k = 1:nSignals
if length(dataVectors{k}.data) > maxSize
maxSize = length(dataVectors{k}.data);
end
end
Now, I would make the data 2D by elevating it to power of two (or three, your choice). This is just a very simple transformation. But you could alternatively use kernel methods here and project each vector against the rest; however, I don't think this is necessary, and if your data is really big, it could be inefficient. For now, raising the data to the power of two should do the trick. The result is stored in a second dimension.
projDegree = 2;
projData = zeros(nSignals, maxSize, 2).*NaN;
for k = 1:nSignals
vecSize = length(dataVectors{k}.data);
projData(k, 1:vecSize, 1) = dataVectors{k}.data;
projData(k, 1:vecSize, 2) = dataVectors{k}.data.*projDegree;
end
projData = reshape(projData, [], 2);
Here, projData will have in row 1 and column 1, the original data of the first whisker (or signal as I call it here), and column 2 will have the new dimension. Let's suppose that you have 8 whiskers in total, then, projData will have the data of the first whisker in row 1, 9, 17, and so on. The data of the second whisker in row 2, 10, 18, and so forth. That is important if you want to work your way back to the original data. Also, you can try with different projDegrees but I doubt it will make a lot of difference.
Now we perform k-means on the 2D data; however, we provide the initial points instead of letting it determine them with k-means++. The initial points, as I propose here, are the first data point of each vector for each whisker. In this manner, k-means will depart from there and will move to clusters means accordingly. We save the results in idxK.
idxK = kmeans(projData,nSignals, 'Start', projData(1:nSignals, :));
And there you have it. The variable idxK will tell you which data point belongs to what cluster.
Below is a working example of my proposed solution. The first part is simply trying to produce data that looks like your data, you can skip it.
rng(9, 'twister')
nSignals = 8; % number of whiskers
n = 1000; % number of data points
allData = zeros(nSignals, n); % all the data will be stored here
% this loop will just generate some data that looks like yours
for k = 1:nSignals
x = sort(rand(1,n));
nPeriods = round(rand*9)+1; % the sin can have between 1-10 periods
nShiftAmount = round(randn*30); % shift between ~ -100 to +100
y = sin(x*2*pi*nPeriods) + (randn(1,n).*0.5);
y = y + nShiftAmount;
allData(k, :) = y;
end
nanIdx = round(rand(1, round(n*0.05)*nSignals).*((n*nSignals)-1))+1;
allData(nanIdx) = NaN; % about 5% of the data is now missing
figure(1);
for k = 1:nSignals
nanIdx = ~isnan(allData(k, :));
dataVectors{k}.data = allData(k, nanIdx);
plot(dataVectors{k}.data, 'kx'), hold on;
end
% determine the max size
maxSize = -Inf;
for k = 1:nSignals
if length(dataVectors{k}.data) > maxSize
maxSize = length(dataVectors{k}.data);
end
end
% making the data now into two dimensions and NaN pad
projDegree = 2;
projData = zeros(nSignals, maxSize, 2).*NaN;
for k = 1:nSignals
vecSize = length(dataVectors{k}.data);
projData(k, 1:vecSize, 1) = dataVectors{k}.data;
projData(k, 1:vecSize, 2) = dataVectors{k}.data.*projDegree;
end
projData = reshape(projData, [], 2);
figure(2); plot(projData(:,1), projData(:,2), 'kx');
% run k-means using the first points of all measure as the initial points
idxK = kmeans(projData,nSignals, 'Start', projData(1:nSignals, :));
figure(3);
liColors = [{'yx'},{'mx'},{'cx'},{'bx'},{'kx'},{'gx'},{'rx'},{'gd'}];
for k = 1:nSignals
plot(projData(idxK==k,1), projData(idxK==k,2), liColors{k}), hold on;
end
% plot results on original data
figure(4);
for k = 1:nSignals
plot(projData(idxK==k,1), liColors{k}), hold on;
end
Let me know if this helps.
I want to execute a logical statement in a for loop.
If I have an image of size 480(height) by 640(width), I would like to scan the image in a section, this section being the whole height between width 635 to 640. I would like to find out if there are any (x,y) coordinates which are found in the cell "cellData". This cell contains a whole list of (x,y) coordinates which can be found in the whole image.
h = height
w = width
for h = 1:480
for w = 635:640
if cellData = 1;
cellData(x,y) = SecondCoordinate(x,y);
end
end
end
Basically I am trying to select a point in the section I mentioned above. The point must be from the cell "cellData". Am i doing this correctly? Will the first (x,y) coordinates that the code detects from the cellData be stored as a (x,y) coordinate in "SecondCoordinate(x,y)"?
You should have a look at find. It's not only much shorter, but also more efficient than your current approach with nested for loops.
[row, col] = find(cellData) would return all the coordinates where cellData is not zero.
If cellData contains other values than just ones and zeros, it would be
[row, col] = find(cellData ~= 0)
I am plotting a 7x7 pixel 'image' in MATLAB, using the imagesc command:
imagesc(conf_matrix, [0 1]);
This represents a confusion matrix, between seven different objects. I have a thumbnail picture of each of the seven objects that I would like to use as the axes tick labels. Is there an easy way to do this?
I don't know an easy way. The axes properties XtickLabel which determines the labels, can only be strings.
If you want a not-so-easy way, you could do something in the spirit of the following non-complete (in the sense of a non-complete solution) code, creating one label:
h = imagesc(rand(7,7));
axh = gca;
figh = gcf;
xticks = get(gca,'xtick');
yticks = get(gca,'ytick');
set(gca,'XTickLabel','');
set(gca,'YTickLabel','');
pos = get(axh,'position'); % position of current axes in parent figure
pic = imread('coins.png');
x = pos(1);
y = pos(2);
dlta = (pos(3)-pos(1)) / length(xticks); % square size in units of parant figure
% create image label
lblAx = axes('parent',figh,'position',[x+dlta/4,y-dlta/2,dlta/2,dlta/2]);
imagesc(pic,'parent',lblAx)
axis(lblAx,'off')
One problem is that the label will have the same colormap of the original image.
#Itmar Katz gives a solution very close to what I want to do, which I've marked as 'accepted'. In the meantime, I made this dirty solution using subplots, which I've given here for completeness. It only works up to a certain size input matrix though, and only displays well when the figure is square.
conf_mat = randn(5);
A = imread('peppers.png');
tick_images = {A, A, A, A, A};
n = length(conf_mat) + 1;
% plotting axis labels at left and top
for i = 1:(n-1)
subplot(n, n, i + 1);
imshow(tick_images{i});
subplot(n, n, i * n + 1);
imshow(tick_images{i});
end
% generating logical array for where the confusion matrix should be
idx = 1:(n*n);
idx(1:n) = 0;
idx(mod(idx, n)==1) = 0;
% plotting the confusion matrix
subplot(n, n, find(idx~=0));
imshow(conf_mat);
axis image
colormap(gray)