Say I have an boundary image in a logical matrix where true means boundary and false means region interior. The image encodes a tessellation of a 2D domain.
I was wondering if there is a compact way in MATLAB to "fix" those pixel neighborhoods where the separation between adjacent regions is only 4-connected and transform them into 8-connected in a manner that preserves the topology of the tessellation.
I believe this can be done with LUTs, but I'm not sure how to proceed. Do I have to, and if so, how do I exactly evaluate all the 3x3 pixel regions where the connectivity is only 4-wise to fill-in the corresponding pixels?
My proposed solution: use BWHITMISS to find the pixels whose neighborhood is at least 4-connected, dilate the result with a rectangular-shaped structuring element to convert those neighborhoods to 8-connected, finally we combine with the original image using logical-OR.
Example:
bw = [
0 0 0 1 0 1 0
0 0 1 1 1 1 1
0 1 1 1 0 1 0
0 0 1 0 1 0 0
0 1 1 0 0 0 0
0 0 1 0 1 1 1
0 0 1 0 0 1 0
];
hm = bwhitmiss(bw, [0 1 0; 1 1 1; 0 1 0]); %# [-1 1 -1; 1 1 1; -1 1 -1]
bw2 = imdilate(hm,ones(3)) | bw;
We can visualize the result:
[r c] = find(hm);
subplot(121), imshow(bw), hold on, plot(c(:),r(:),'o')
subplot(122), imshow(bw2)
Related
I have a uint16 image of size 512*512. The problem is that the image is all black except there is a medium object of uniform intensity of value 40000. This object has a black hole and I need to extract the indexes of this hole.
So the matrix is in the form of all zeroes, and in some region of the matrix we have a submatrix filled with the value 40000, and in this submatrix we have another submatrix of zeroes. This zero submatrix is the required one to be extracted in terms of its indices. Any suggestions?
A very simple solution would be to use bwlabel. For example:
>> data = 40000.*[0 0 0 0 0; 0 1 1 1 0; 0 1 0 1 0; 0 1 1 1 0; 0 0 0 0 0]
data =
0 0 0 0 0
0 40000 40000 40000 0
0 40000 0 40000 0
0 40000 40000 40000 0
0 0 0 0 0
>> labelMatrix = bwlabel(~data)
labelMatrix =
1 1 1 1 1
1 0 0 0 1
1 0 2 0 1
1 0 0 0 1
1 1 1 1 1
>> holeIndex = find(labelMatrix == 2)
holeIndex =
13
The success of this is based on many assumptions, such as there only being one bright object and one hole in it. A more robust solution would require an example of the images you're analyzing, and might involve some preprocessing of the image and the use of functions such as regionprops to identify areas of the image.
I have got a 2D matrix. There is some region in the matrix where the elements are non-zero, in particular everywhere around the edge they are zero.
I plot the matrix using image as a colorplot and would like to add the curve that shows the boundary between non-zero values to zero values in the matrix. Is there any neat way to do this without loops?
This looks like a job for convhull :
To illustrate this code i'll take a dummy example :
A=zeros(10);
B=binornd(1,0.5,8,8);
A(2:end-1,2:end-1)=B
A =
0 0 0 0 0 0 0 0 0 0
0 0 0 1 0 0 0 0 0 0
0 0 1 1 1 1 1 1 0 0
0 0 1 1 0 0 0 0 1 0
0 0 0 1 0 0 0 1 0 0
0 1 0 0 0 0 0 1 0 0
0 0 0 1 1 1 1 1 1 0
0 0 1 0 1 1 1 1 0 0
0 1 0 1 1 1 1 0 1 0
0 0 0 0 0 0 0 0 0 0
1/ Find the locations of all non zero entries :
[row,col]=find(A);
2/ Take the convex hull of these locations
k=convhull(row,col);
3/ Plot the convex hull (I plot the non zero points aswell but in your problem it will be your image points)
plot(row(k),col(k),'r-',row,col,'b*')
Result :
Another option is using the image processing toolbox and the bwperim function. This will work if you know that your area is completely closed (i.e. has no holes in the boundary)
This is an example using a black and white image, and you have 2 options: fill the inner gaps before, or not. You can see in the result the differences.
A = imread('circles.png');
Afill=imfill(A,'holes'); % optional
Abound1=bwperim(Afill);
Abound2=bwperim(A);
imshow([A,Abound, Abound2])
You can plot one on top of the other with:
[x,y]= find(Abound2);
hold on
image(A*255) %// If A is logical, else use just A (not *255)
colormap('gray')
plot(y,x,'r.')
hold off
axis tight
If you have a gray-scale image (or a matrix with a single value in each position (2D matrix), then you can binarize it first by either:
If you know everything outside your object is EXACTLY zero
A=yourA>0;
If you want to separate your object from the background, and the background is not exactly zero by A=im2bw(yourA,level), by choosing your own level, or letting Otsu do it for you with level=graythresh(yourA)
I am very interested in fingerprint verification and studying minutia extraction at present. I have found the following code online and wonder if someone would be kind enough to explain it? I have looked up centroid, regionprops etc, I understand these a little but the code below has me puzzled!
fun=#minutie;
L = nlfilter(K,[3 3],fun);
%% Termination
LTerm=(L==1);
imshow(LTerm)
LTermLab=bwlabel(LTerm);
propTerm=regionprops(LTermLab,'Centroid');
CentroidTerm=round(cat(1,propTerm(:).Centroid));
imshow(~K)
set(gcf,'position',[1 1 600 600]);
hold on
plot(CentroidTerm(:,1),CentroidTerm(:,2),'ro')
%% Bifurcation
LBif=(L==3);
LBifLab=bwlabel(LBif);
propBif=regionprops(LBifLab,'Centroid','Image');
CentroidBif=round(cat(1,propBif(:).Centroid));
plot(CentroidBif(:,1),CentroidBif(:,2),'go')
The code first filters the binary image with a neighborhood of 3x3 pixels. nfilter is a moving filter function. It will go through all the pixels in the image given as argument and apply an operation based on the values of the neighboring pixels.
I don't know the exact content of the minutie filter, but judging by the rest of the code, it probably counts the pixels with a value of 1 in the neighborhood of all 1s. In other words it will be equal to one at the end of a segment, and equal to 3 when there are 3 branches (a bifurcation).
Example:
Let a filter sum up the ones in the neighborhood, like this:
sum(block(1,1:3), block(3,1:3), block(2,1), block(2,3))*block(2, 2);
where block denotes a neighborhood around each pixel of the binary image.
In the left matrix below (if you ignore the boundary exceptions) there is one position with a one that has exactly one 1 in its 3x3 neighborhood, in the right matrix, there is one position with a one that has exactly three 1s in its 3x3 neighborhood.
[0 0 0 0 0 [0 0 1 0 0
0 0 0 0 0 0 0 1 0 0
0 0 1 0 0 1 1 1 0 0
0 0 1 0 0 0 0 1 0 0
0 0 1 0 0] 0 0 1 0 0]
The filtered output would be:
[0 0 0 0 0 [0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 3 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0] 0 0 0 0 0]
It found a termination in the left matrix, and a bifurcation in the right matrix.
The filtered image are then thresholded at the value 1 and 3, then the use of bwlabel and regionprops is somewhat mysterious to me† since bifurcations and terminations are single points, their position is simply their index. I think you could simply achieve the detection of the coordinates of the terminations and bifurcation using something like:
[It Jt]= find(L==1);
[Ib Jb]= find(L==3);
† one reason I can think of is that coordinates in images and arrays are different in matlab, and these two function output coordinates in the image format, which is easier to plot on top of the original image.
Given a matrix where 1 is the current subset
test =
0 0 0 0 0 0
0 0 0 0 0 0
0 0 1 1 0 0
0 0 1 1 0 0
0 0 0 0 0 0
0 0 0 0 0 0
Is there a function, or quick method to get change the subset to the boundary of the current subset?
Eg. Get this subset from 'test' above
test =
0 0 0 0 0 0
0 1 1 1 1 0
0 1 0 0 1 0
0 1 0 0 1 0
0 1 1 1 1 0
0 0 0 0 0 0
In the end I just want to get the minimum of the cells surrounding a subset of a matrix. Sure I could loop through and get the minimum of the boundary (cell by cell), but there must be a way to do it with the method i've shown above.
Note the subset WILL be connected, but may not be rectangular. This may be the big catch.
This is a possible subset.... (Would pad this with a NaN border)
test =
0 0 0 0 0 0
0 0 0 0 0 0
0 0 1 1 0 0
0 0 1 1 0 0
0 0 1 1 1 1
0 0 1 1 1 1
Ideas?
The basic steps I'd use are:
Perform a dilation on the shape to get a new area which is the shape plus its boundary
Subtract the original shape from the dilated shape to leave just the boundary
Use the boundary to index your data matrix, then take the minimum.
Dilation
What I want to do here is pass a 3x3 window over each cell and take the maximum value in that window:
[m, n] = size(A); % assuming A is your original shape matrix
APadded = zeros(m + 2, n + 2);
APadded(2:end-1, 2:end-1) = A; % pad A with zeroes on each side
ADilated = zeros(m + 2, n + 2); % this will hold the dilated shape.
for i = 1:m
for j = 1:n
mask = zeros(size(APadded));
mask(i:i+2, j:j+2) = 1; % this places a 3x3 square of 1's around (i, j)
ADilated(i + 1, j + 1) = max(APadded(mask));
end
end
Shape subtraction
This is basically a logical AND and a logical NOT to remove the intersection:
ABoundary = ADilated & (~APadded);
At this stage you may want to remove the border we added to do the dilation, since we don't need it any more.
ABoundary = ABoundary(2:end-1, 2:end-1);
Find the minimum data point along the boundary
We can use our logical boundary to index the original data into a vector, then just take the minimum of that vector.
dataMinimum = min(data(ABoundary));
You should look at this as morphology problem, not set theory. This can be solved pretty easily with imdilate() (requires the image package). You basically only need to subtract the image to its dilation with a 3x3 matrix of 1.
octave> test = logical ([0 0 0 0 0 0
0 0 0 0 0 0
0 0 1 1 0 0
0 0 1 1 0 0
0 0 1 1 1 1
0 0 1 1 1 1]);
octave> imdilate (test, true (3)) - test
ans =
0 0 0 0 0 0
0 1 1 1 1 0
0 1 0 0 1 0
0 1 0 0 1 1
0 1 0 0 0 0
0 1 0 0 0 0
It does not, however, pads with NaN. If you really want that, you could pad your original matrix with false, do the operation, and then check if there's any true values in the border.
Note that you don't have to use logical() in which case you'll have to use ones() instead of true(). But that takes more memory and has worse performance.
EDIT: since you are trying to do it without using any matlab toolbox, take a look at the source of imdilate() in Octave. For the case of logical matrices (which is your case) it's a simple usage of filter2() which belongs to matlab core. That said, the following one line should work fine and be much faster
octave> (filter2 (true (3), test) > 0) - test
ans =
0 0 0 0 0 0
0 1 1 1 1 0
0 1 0 0 1 0
0 1 0 0 1 1
0 1 0 0 0 0
0 1 0 0 0 0
One possible solution is to take the subset and add it to the original matrix, but ensure that each time you add it, you offset its position by +1 row, -1 row and +1 column, -1 column. The result will then be expanded by one row and column all around the original subset. You then use the original matrix to mask the original subet to zero.
Like this:
test_new = test + ...
[[test(2:end,2:end);zeros(1,size(test,1)-1)],zeros(size(test,1),1)] + ... %move subset up-left
[[zeros(1,size(test,1)-1);test(1:end-1,2:end)],zeros(size(test,1),1)] + ... %move down-left
[zeros(size(test,1),1),[test(2:end,1:end-1);zeros(1,size(test,1)-1)]] + ... %move subset up-right
[zeros(size(test,1),1),[zeros(1,size(test,1)-1);test(1:end-1,1:end-1)]]; %move subset down-right
test_masked = test_new.*~test; %mask with original matrix
result = test_masked;
result(result>1)=1; % ensure that there is only 1's, not 2, 3, etc.
The result for this on your test matrix is:
result =
0 0 0 0 0 0
0 1 1 1 1 0
0 1 0 0 1 0
0 1 0 0 1 1
0 1 0 0 0 0
0 1 0 0 0 0
Edited - it now grabs the corners as well, by moving the subset up and to the left, up and to the right, down then left and down then right.
I expect this would be a very quick way to achieve this - it doesn't have any loops, nor functions - just matrix operations.
I am trying to build square band matrices using blkdiag or spdiags, but can't figure out how. I find the documentation for spdiags a bit confusing, and am not sure I can build these matrices in a simple call to blkdiag.
I would like to build a square band matrix from two parameters:
The width of the band
Matrix size
For example:
band_width = 2;
matrix size = 9;
Result:
[1 1 1 0 0 0 0 0 0]
[1 1 1 1 0 0 0 0 0]
[1 1 1 1 1 0 0 0 0]
[0 1 1 1 1 1 0 0 0]
[0 0 1 1 1 1 1 0 0]
[0 0 0 1 1 1 1 1 0]
[0 0 0 0 1 1 1 1 1]
[0 0 0 0 0 1 1 1 1]
[0 0 0 0 0 1 1 1 1]
[0 0 0 0 0 0 1 1 1]
A tricky one-line way to create a matrix like this is with convolution:
M = sign(conv2(eye(matrix_size),ones(band_width+1),'same'));
An identity matrix is created of the given size, then convolved in 2-D with a square matrix of ones, then converted to zeroes and ones by taking the sign.
The above is fine for making relatively small non-sparse matrices. For larger matrices the convolution may get expensive and you would probably want to represent the result as a sparse matrix instead. Here is how you can do this in a general way using SPDIAGS:
M = spdiags(ones(matrix_size,2*band_width+1),...
-band_width:band_width,matrix_size,matrix_size);