I have an image include 4 values {3,-3,1,-1} as figure
Let call the index of pixel that its values equals 1 or -1 pixel in contour. These pixels will create a contour that surrounds the yellow color (-3). Now, I want to find all index pixels in the contour and plus padding position inward and outward contour. As the red color, padding is set 1, hence, the index of these pixels include pixel in the contour {1,-1} and padding index as the red color. In that task, I want to find all pixel indices. How to implement that idea in matlab code. This is my code to find the index in the contour
%% Let define the image I
idx=find(I==1|I==-1);
padding=1;
%%Continue
Update: My expected result as the above figure in white region. Hence, the indices are such as 13,14,15,..21,24,...
UPDATE
Firstly, thank Andrew and Rayryeng for your answer. I would like to extedn my problem. As the above description, the contour is created by {1,-1}. Now, I want to ignore 1 and -1, so the image I only has {3,-3}. And I defined the contour is pixel in the edge of {3,-3} such as figure. Keep the same idea of padding and pixel index. How to find the indices of pixels in contour and near contour (call narrow band of contour)(expected result is white color)
Not too difficult you are on the right track. If you have the image processing toolbox, I recommend taking a look at morphological operators. Specifically you want to use imdilate my code has all the details you need.
%rather than using find, we create a binary mask. Its not the indicies of
%the matching elements as find gives. its is 1/true if the value matches the
%criteria, and 0/false otherwise.
mask = (im=1 | im=-1);
%create a 3x3 rectangle structuring element. We use a 3x3 because we want
%to expand the image by one pixel. basically the structring element (Strel)
%is our kernal, if you know image processing this is the same thing.
%a = [0 0 0 0;
% 0 1 1 1;
% 0 1 1 1;
% 0 1 1 1];
%our kernal is center at 2,2 (for this example) which are these elements
% 0 0 0 of a think a(1:3,1:3) now what the dialate operation
% 0 1 1 says is, if the majority of these pixels are ones... they
% 0 1 1 should probabaly all be ones so all those 0s will become ones
%the size of the kernal 3x3 ensures we are only growing our image one
%pixel, hope that makes sense
se = strel('square',3);
%now we dilate, or 'expand' our mask with our structuring element
expanded_mask = imdilate(mask,se);
%if you still want the indicies you can use find on our expanded mask
idx = find(expanded_mask==1);
EDIT: without morphological operations/image processing toolbox
This method uses lots of for loops, so it isn't the fastest, and doens't do error checking, but it will work. My dilate function says if the majority of the pixels are ones make them all ones.
function expanded_mask=DilateBinaryImage(bin_im, kernal_size)
[max_row,max_col] = size(bin_im);
%since we are opening the mask (only adding 1s), we can start off with the
%same values of the mask, and simply add extra 1's as needed
expanded_mask = bin_im;
%we don't want to go off the edge of our image with this kernal
%so we offset it a bit
kern_padding = floor(kernal_size/2);
%this ignores the edges
for (curr_row=kern_padding+1:1:max_row - kern_padding)
for (curr_col=kern_padding+1:1:max_col - kern_padding)
%we do 2 sums, one for rows, one for columns
num_ones = sum(sum(bin_im(curr_row-kern_padding:curr_row+kern_padding,curr_col-kern_padding:curr_col+kern_padding)));
%if the majority of vlaues are 1, we use floor to help with corner
%cases
if (num_ones >= floor((kernal_size*kernal_size)/2))
%make all the values one
expanded_mask(curr_row-kern_padding:curr_row+kern_padding,curr_col-kern_padding:curr_col+kern_padding) = 1;
end
end
end
end
and then called it like this
kernal_size= 3;
mask = (I==1 | I==-1);
expanded_mask = DilateBinaryImage(mask, kernal_size);
idx = find(expanded_mask==1);
my dilate function doesn't work at the edges of the binary image. it just copies them exactly.
Lets say your image is N-by-M pixles. In MATLAB arrays are stored in column order (see http://www.mathworks.com/help/matlab/math/matrix-indexing.html for more information). You can use I in column format as follows. First you contour pixels are given by
idx=find(I(:)==1|I(:)==-1);
Now, if you wish to pad downward and upward it is quite simple:
idx_up=idx - padding;
idx_up = idx_up(idx_up>0);
idx_down=idx + padding;
idx_down = idx_down(idx_down<=N*M);
Note that idx_up and idx_down will also contain contour pixels.
Similarly you can pad to the left\right:
idx_left=idx - padding*N;
idx_left = idx_left(idx_left>0);
idx_right=idx + padding*N;
idx_right = idx_right(idx_right<=N*M);
And combine the overall pixels:
PaddedContour = false(N,M);
PaddedContour(unique([idx;idx_up;idx_down;idx_left;idx_right])) = true;
Related
I have some problems that require me to manipulate polygons using operations such as translating, dilating, rotating, and shearing. The data I have is actually on state boundaries and geometries from data.gov on the state of Delaware. The function delaware.m returns a cell array (1x3 cell) of polygon matrices describing the shape of the state of Delaware, and this is the shape I need to do operations on. I will post the specific questions so you can get a sense of what I'm being asked of, but I'm still asking for more general guidance than a specific answer to each question.
Translate the state of Delaware so that its center is approximately at the origin.
Dilate the translated state of Delaware so that it fits inside a square of side length one centered at the origin.
Rotate the translated, dilated state of Delaware so that New Castle County is at the bottom and Sussex is at the top.
Dilate the translated, dilated, rotated state of Delaware without changing its area, so that it is about as wide as it is tall.
Shear the translated, dilated, rotated, dilated state of Delaware the northernmost tip is at least 2 units to the right of the southernmost tip.
The thing is, I know how to do all these operations in Matlab with just a single polygon/matrix. I am mostly struggling with how to use this with the cell array.
For example, say I have matrix S.
newS=S+[1;2]; %move S one unit to the right and two units up
R=[sqrt(2)/2 -sqrt(2)/2; sqrt(2)/2 sqrt(2)/2];
newS=R*S %rotate the polygon by 45 degrees
D = [alpha 0; 0 beta];
%alpha is the dilation scaling the x direction and beta in the y direction
%left multiply S by this dilation matrix to dilate along the cardinal axes
Sh=[1 y; 0 1] %y controls the amount of shearing
%left multiply by S to shear a shape along the x-axis relative to the y-axis
So for example, when I try to do an operation for moving the shape up/down/left/right as I described above for the cell array, I get the error message
Undefined operator '+' for input arguments of type 'cell'.
I also tried:
DEBoundary1 = cellfun(#sum, DEBoundary, [75.562;-39.6]);
%this is how much I wanted to move the polygons
But got:
>> Lab_code
Error using cellfun
All of the input arguments must be of the same size and shape.
Previous inputs had size 1 in dimension 1. Input #3 has size 2
I suppose in general, is there an easy way to take these operations I already know and apply them to a cell array consisting of polygon matrices? Or do I have to go about it a different way?
I believe this is what you're trying to do with your + example:
DEBoundary = {[0 1 -1 0; 1 -1 -1 1], [0 -1 1 0; 1 1 1 1]};
offset = [3;-2];
DEBoundary1 = cellfun(#(c) c + offset, DEBoundary, 'UniformOutput', false)
What this does is:
cellfun(#(c) % c is each element in the cell
c + offset % add the offset to each element
, DEB % The cell array to operate on
'UniformOutput', 0) % Specifies that the output is a cell and not a scalar
Try it online!
If you thing cellfun is confusing, then you may do this manually:
DEBoundary1 = cell(size(DEBoundary))
for i = 1:numel(DEBoundary)
DEBoundary1{i} = DEBoundary{i} + offset;
end
This should work with multiplication and other operations as well, as long as the dimensions match (but that's a mathematical question, not MATLAB specific).
I am implementing a simple algorithm to do in-painting on a "damaged" image. I have a predefined mask that specifies the area which needs to be fixed. My strategy is to start at the border of the masked area and in-paint each pixel with the central mean of its neighboring non-zero pixels, repeating until there's no unknown pixels left.
function R = inPainting(I, mask)
H = [1 2 1; 2 0 2; 1 2 1];
R = I;
n = 1;
[row,col,~] = find(~mask); %Find zeros in mask (area to be inpainted)
unknown = horzcat(row, col)';
while size(unknown,2) > 0
new_unknown = [];
new_R = R;
for u = unknown
r = u(1);
c = u(2);
nb = R(max((r-n), 1):min((r+n), end), max((c-n),1):min((c+n),end));
nz = nb~=0;
nzs = sum(nz(:));
if nzs ~= 0 %We have non-zero neighbouring pixels. In-paint with average.
new_R(r,c) = sum(nb(:)) / nzs;
else
new_unknown = horzcat(new_unknown, u);
end
end
unknown = new_unknown;
R = new_R;
end
This works well, but it's not very efficient. Is it possible to vectorize such an approach, using mostly matrix operations? Does someone know of a more efficient way to implement this algorithm?
If I understand your problem statement, you are given a mask and you wish to fill in these pixels in this mask with the mean of the neighbourhood pixels that surround each pixel in the mask. Another constraint is that the image is defined such that any pixels that belong to the mask in the same spatial locations are zero in this mask. You are starting from the border of the mask and are propagating information towards the innards of the mask. Given this algorithm, there is unfortunately no way you can do this with standard filtering techniques as the current time step is dependent on the previous time step.
Image filtering mechanisms, like imfilter or conv2 can't work here because of this dependency.
As such, what I can do is help you speed up what is going on inside your loop and hopefully this will give you some speed up overall. I'm going to introduce you to a function called im2col. This is from the image processing toolbox, and given that you can use imfilter, we can use this function.
im2col creates a 2D matrix such that each column is a pixel neighbourhood unrolled into a single vector. How it works is that each pixel neighbourhood in column major order is grabbed, so we get a pixel neighbourhood at the top left corner of the image, then move down one row, and another row and we keep going until we reach the last row. We then move one column over and repeat the same process. For each pixel neighbourhood that we have, it gets unrolled into a single vector, and the output would be a MN x K matrix where you have a neighbourhood size of M x N for each pixel neighbourhood and there are K neighbourhoods.
Therefore, at each iteration of your loop, we can unroll the current inpainted image's pixel neighbourhoods into single vectors, determine which pixel neighborhoods are non-zero and from there, determine how many zero values there are for each of these selected pixel neighbourhood. After, we compute the mean for these non-zero columns disregarding the zero elements. Once we're done, we update the image and move to the next iteration.
What we're going to need to do first is pad the image with a 1 pixel border so that we're able to grab neighbourhoods that extend beyond the borders of the image. You can use padarray, also from the image processing toolbox.
Therefore, we can simply do this:
function R = inPainting(I, mask)
R = double(I); %// For precision
n = 1;
%// Change - column major indices
unknown = find(~mask); %Find zeros in mask (area to be inpainted)
%// Until we have searched all unknown pixels
while numel(unknown) ~= 0
new_R = R;
%// Change - take image at current iteration and
%// create columns of pixel neighbourhoods
padR = padarray(new_R, [n n], 'replicate');
cols = im2col(padR, [2*n+1 2*n+1], 'sliding');
%// Change - Access the right pixel neighbourhoods
%// denoted by unknown
nb = cols(:,unknown);
%// Get total sum of each neighbourhood
nbSum = sum(nb, 1);
%// Get total number of non-zero elements per pixel neighbourhood
nzs = sum(nb ~= 0, 1);
%// Replace the right pixels in the image with the mean
new_R(unknown(nzs ~= 0)) = nbSum(nzs ~= 0) ./ nzs(nzs ~= 0);
%// Find new unknown pixels to look at
unknown = unknown(nzs == 0);
%// Update image for next iteration
R = new_R;
end
%// Cast back to the right type
R = cast(R, class(I));
I have another problem today:
I have a binary matrix t, in which 1 represents a river channel, 0 represents flood plane and surrounding mountains:
t = Alog>10;
figure
imshow(t)
axis xy
For further calculations, I would like to expand the area of the riverchannel a few pixels in each direction. Generally speaking, I want to have a wider channel displayed in the image, to include a larger region in a later hydraulic model.
Here is my attempt, which does work in certain regions, but in areas where the river runs diagonal to the x-y axis, it does not widen the channel. There seems to be a flow in approaching this, which I cannot quite grasp.
[q,o] = find(t == 1);
qq = zeros(length(q),11);
oo = zeros(length(o),11);
% add +-5 pixel to result
for z=1:length(q)
qq(z,:) = q(z)-5:1:q(z)+5;
oo(z,:) = o(z)-5:1:o(z)+5;
end
% create column vectors
qq = qq(:);
oo = oo(:);
cords = [oo qq]; % [x y]
% remove duplicates
cords = unique(cords,'rows');
% get limits of image
[limy limx] = size(t);
% restrict to x-limits
cords = cords(cords(:,1)>=1,:);
cords = cords(cords(:,1)<=limx,:);
% restrict to y-limits
cords = cords(cords(:,2)>=1,:);
cords = cords(cords(:,2)<=limy,:);
% test image
l = zeros(size(img));
l(sub2ind(size(l), cords(:,2)',cords(:,1)')) = 1;
figure
imshow(l)
axis xy
This is the image I get:
It does widen the channel in some areas, but generally there seems to be a flaw with my approach. When I use the same approach on a diagonal line of pixels, it will not widen the line at all, because it will just create more pairs of [1 1; 2 2; 3 3; etc].
Is there a better approach to this or even something from the realm of image processing?
A blur filter with a set diameter should be working somewhat similar, but I could not find anything helpful...
PS: I wasn't allowed to add the images, although I already have 10 rep, so here are the direct links:
http://imageshack.us/a/img14/3122/channelthin.jpg
http://imageshack.us/a/img819/1787/channelthick.jpg
If you have the image processing toolbox, you should use the imdilate function. This performs the morphological dilation operation. Try the following code:
SE = strel('square',3);
channelThick = imdilate(channelThin,SE);
where SE is a 3x3 square structuring element used to dilate the image stored in channelThin. This will expand the regions in channelThin by one pixel in every direction. To expand more, use a larger structuring element, or multiple iterations.
You may apply morphological operations from image processing. Morphological dilation can be used in your example.
From the image processing toolbox, you can use bwmorth command BW2 = bwmorph(BW,'dilate') or imdilate command IM2 = imdilate(IM,SE).
Where IM is your image and SE is the structuring element. You can set SE = ones(3); to dilate the binary image by "one pixel" - but it can be changed depending on your application. Or you can dilate the image several times with the same structuring element if needed.
I have a matrix X that represents an image that was affected by noise. I also have a boolean matrix M that represents which pixels were affected by noise. What I want to do is to set every 'corrupted' pixel to the mean of its eight neighboring pixels.
Corrupted pixels are guaranteed to always be surrounded by uncorrupted ones, and also none of the pixels on the borders of the image are corrupted. What function can I used to write a vectorised version of this?
For your situation, this should perform quite fast
fixed = conv2 (image, [1 1 1; 1 0 1; 1 1 1]/8, "same")
# mask is a logical matrix for the corrupted pixels
image(mask) = fixed(mask)
Explanation: a mean filter is done with the conv2 function. To calculate the average of a pixel and its neighbors, the kernel used is ones (3) / 9 which means that 1/9 of each pixel value is used to calculate the new value. Since you don't want to count the center pixel in the average, you make its value 0 (in the kernel), and the others to 1/8.
This is probably not the most effective solution, but it should work.
N = size(M, 1);
target_ind = find(M);
offset = [-N-1, -N, -N+1, -1, 0, 1, N-1, N, N+1];
area_ind = bsxfun(#plus, offset, target_ind);
X(target_ind) = median(X(area_ind), 2);
Since all corrupted pixels are guaranteed to be surrounded by pixels, we can rather easily compute the linear indices of each corrupted pixel's neighbors. Here I've assumed that X is a grayscale image.
If I has more than one channel, then we could loop over each channel and add an offset to
target_ind and area_ind each time:
for i = 1:size(X, 3)
chan_offset = (i - 1)*size(X, 1)*size(X, 2) % Add the number of elements in previous channels to get indices in the current channel
X(target_ind + chan_offset) = median(X(area_ind + chan_offset), 2);
end
alt text http://internationalpropertiesregistry.com/Server/showFile.php?file=%2FUpload%2Fstatistics.gifc49ca28823a561a41d09ef9adbb5e0c5.gif
The unit of x-axis is hours (h), and there are 24 hours in total.
The unit of y-axis is millions (m).
How do I calculate the area under the red curve in the image in units of m*h?
Important UPDATE
Only the image is readily available (not the data), and I want to calculate the area programmatically.
Here's an interesting solution :). Btw, it uses bwfill (similar to imfill) which needs some user interaction.
Code
%# Constants
gray_value_curve = 2;
gray_value_box = 3;
area_box_in_units = 10;
%# Read the image
I = imread('C:\p23\graph.gif');
%# Find the area of a unit block
figure(1);
imshow(I,[]);
[BS sq_elem] = bwfill;
imshow(BS,[]);
%# Get the dimensions to make the estimate more accurate
X = zeros(size(BS));
X(sq_elem) = 1;
s = regionprops(X,'Area','BoundingBox');
block_area = s.Area + 2*(s.BoundingBox(3)-1) + 2*(s.BoundingBox(4)-1) + 4;
%#Find the area under the curve
I( ~(I == gray_value_curve | I == gray_value_box) ) = 0;
figure(2);
imshow(I,[]);
[BA area_curve_elem] = bwfill;
imshow(BA,[]);
%# Area under the curve
curve_area = numel(area_curve_elem);
%# Display the area in the required units
area = area_box_in_units*curve_area/block_area;
disp(area);
Output
113.5259
Figure 1
Figure 2
The difficulty with creating a fully-automated solution is that it would require you to hardcode into your solution certain assumptions about the input images you are going to process. If these assumptions don't hold for all the potential images you may come across, the fully-automated solution won't give trustworthy results, and trying to extend the fully-automated solution to handle all possible inputs will likely cause it to bloat into an incomprehensible and complicated mess of code.
When in doubt about the variability in features of your input images, a solution like Jacob's with some user interaction is generally best. If you can be certain that the features of your input images follow a strict set of rules, then an automated solution can be considered.
As an example, below is some automated code I wrote to approximate the area under the red curve in your graph. Since I used the above graph as a guide, there are a number of conditions that must be met for it to work:
The red pixels of the plotted line must be uniquely described in the image as containing green and blue color components equal to 0 and red color components equal to 1.
The green pixels of the grid lines must be uniquely described in the image as containing red and blue color components less than 1 and green color components equal to 1.
The blue pixels of the axes lines must be uniquely described in the image as containing red and green color components equal to 0 and blue color components equal to 1.
The grid and axis lines must always be exactly aligned in a horizontal or vertical direction.
The length of the grid lines must span well over half the width and height of the image.
The x axis must be the longest horizontal blue line in the image.
The grid lines must always be 1 pixel thick.
Subject to the above conditions on the input image, the following code can be used to approximate the area under the red curve without user input:
[img,map] = imread('original_chart.gif'); %# Read the indexed image
[r,c] = size(img); %# Get the image size
redIndex = find((map(:,1) == 1) & ... %# Find the red index value
(map(:,2) == 0) & ...
(map(:,3) == 0))-1;
greenIndex = find((map(:,1) < 1) & ... %# Find the green index value
(map(:,2) == 1) & ...
(map(:,3) < 1))-1;
blueIndex = find((map(:,1) == 0) & ... %# Find the blue index value
(map(:,2) == 0) & ...
(map(:,3) == 1))-1;
redLine = (img == redIndex); %# A binary image to locate the red line
greenLine = (img == greenIndex); %# A binary image to locate the grid lines
blueLine = (img == blueIndex); %# A binary image to locate the axes lines
w = mean(diff(find(sum(greenLine,1) > r/2))); %# Compute unit square width
h = mean(diff(find(sum(greenLine,2) > c/2))); %# Compute unit square height
squareArea = w*h; %# Compute unit square area
[maxValue,maxIndex] = max(redLine); %# Find top edge of red line
x = find(maxValue > 0); %# Find x coordinates of red line
y = maxIndex(maxValue > 0); %# Find y coordinates of red line
[maxValue,maxIndex] = max(sum(blueLine,2)); %# Find row index of x axis
y = maxIndex-y; %# Zero the y coordinate
totalArea = trapz(x,y)/squareArea; %# Compute the area under the curve
Which gives the following results:
squareArea = 460.6 square pixels
totalArea = 169.35 m*h
EXPLANATION:
I'll elaborate more about the steps involved in computing w:
The binary image greenLine is summed along each column using the function SUM, giving a 1-by-c vector where each element is a count of how many grid line pixels are in each column of the image.
The elements of this vector that are greater than r/2 (half the number of rows in the image) indicate columns of the image that contain a vertical grid line. The indices of these columns are found using the function FIND.
The pairwise differences between these column indices are found using the function DIFF. This gives a vector containing the widths (in pixels) of the spaces between grid lines.
Finally, the function MEAN is used to compute the mean width of the spaces between all the grid lines in the image.
When computing h, the only difference is that the sum is performed along each row and r/2 is replaced with c/2 (half the number of columns in the image).
Since you only have the image available, I suggest you integrate by eye: count the number of whole squares underneath the red line.
For each square which the red line intersects, decide whether or not to include it in the count depending on how much lies below the line. Don't try to estimate how much of the square lies beneath the red line, at best that will give you an illusion of greater accuracy.
EDIT: I counted the green squares for you, the answer is 168 m.h
Since that doesn't seem like a "function" that you could integrate I would using a numerical integration technique. I'm always partial to trapz which use the "trapezoidal rule" for numerical integration.
Something like:
area = trapz(data);
should be sufficient.
Hope that helps,
Will