Find and crop defined image areas automatically - image

I want to process an image in matlab
The image consists out of a solid back ground and two specimens (top and bottom side). I already have a code that separate the top and bottom and make it two images. But the part what I don't get working is to crop the image to the glued area only (red box in the image, I've only marked the top one). However, the cropped image should be a rectangle just like the red box (the yellow background, can be discarded afterwards).
I know this can be done with imcrop, but this requires manual input from the user. The code needs to be automated such that it is possible to process more images without user input. All image will have the same colors (red for glue, black for material).
Can someone help me with this?
edit: Thanks for the help. I used the following code to solve the problem. However, I couldn't get rid of the black part right of the red box. This can be fix by taping that part off before making pictures. The code which I used looks a bit weird, but it succeeds in counting the black region in the picture and getting a percentage.
a=imread('testim0.png');
level = graythresh(a);
bw2=im2bw(a, level);
rgb2=bw2rgb(bw2);
IM2 = imclearborder(rgb2,4);
pic_negative = ait_imgneg(IM2);
%% figures
% figure()
% image(rgb2)
%
% figure()
% imshow(pic_negative)
%% Counting percentage
g=0;
for j=1:size(rgb2,2)
for i=1:size(rgb2,1)
if rgb2(i,j,1) <= 0 ...
& rgb2(i,j,2) <= 0 ...
& rgb2(i,j,3) <= 0
g=g+1;
end
end
end
h=0;
for j=1:size(pic_negative,2)
for i=1:size(pic_negative,1)
if pic_negative(i,j)== 0
h=h+1;
end
end
end
per=g/(g+h)
If anyone has some suggestions to improve the code, I'm happy to hear it.

For a straight-forward image segmentation into 2 regions (background, foreground) based on color (yellow, black are prominent in your case), an option can be clustering image color values using kmeans algorithm. For additional robustness you can transform the image from RGB to Lab* colorspace.
The example for your case follows the MATLAB Imape Processing example here.
% read and transform to L*a*b space
im_rgb = double(imread('testim0.png'))./256;
im_lab = applycform(im_rgb, makecform('srgb2lab'));
% keep only a,b-channels and form feature vector
ab = double(lab_I(:,:,2:3));
[nRows, nCols, ~] = size(ab);
ab = reshape(ab,nRows * nCols,2);
% apply k-means for 2 regions, repeat c times, e.g. c = 5
nRegions = 2;
[cluster_idx cluster_center] = kmeans(ab,nRegions, 'Replicates', 5);
% get foreground-background mask
im_regions = reshape(cluster_idx, nRows, nCols);
You can use the resulting binary image to index the regions of interest (or find the boundary) in the original reference image.

Images are saved as matrices. If you know the bounds in pixels of the crop box want to crop you can execute the crop using indexing.
M = rand(100); % create a 100x100 matrix or load it from an image
left = 50;
right = 75;
top = 80;
bottom = 10;
croppedM = M(bottom:top, left:right);
%save croppedm

You can easily get the unknown bounded crop by
1) Contour plotting the image,
2) find() on the result for the max/min X/ys,
3) use #slayton's method to perform the actual crop.
EDIT: Just looked at your actual image - it won't be so easy. But color enhance/threshold your image first, and the contours should work with reasonable accuracy. Needless to say, this requires tweaking to your specific situation.

since you've already been able to seperate the top and bottom, and also able to segment the region you want (including the small part of the right side that you don't want), I propose you just add a fix at the end of the code via the following.
after segmentation, sum each column Blue intensity value, so that you are compressing the image from 2d to 1d. so if original region is width=683 height=59, the new matrix/image will be simply width=683 height=1.
now, you can apply a small threshold to determine where the edge should lie, and apply a crop to the image at that location. Now you get your stats.

Related

How to identify and crop a rectangle inside an image in MATLAB

I have an image which has a rectangle drawn in it. The rectangle can be of any design but the background isn't a single color. It's a photo taken from a phone camera like this one.
I want to crop the to inner picture (scenary) in the image.
How can I do this in MATLAB?
I tried this code
img = im2double(imread('https://i.stack.imgur.com/iS2Ht.jpg'));
BW = im2bw(img);
dim = size(BW)
col = round(dim(2)/2)-90;
row = min(find(BW(:,col)))
boundary = bwtraceboundary(BW,[row, col],'N');
r = [min(boundary) , max(boundary)];
img_cropped = img(r(1) : r(3) , r(2) : r(4) , :);
imshow(img_cropped);
but it works only for one image, this one
and not the one above or this one
I need to find a code which works for any image with a specific rectangle design.Any help will be aprreciated.Thank you
The processing below considers the following:
backgroung is monochrome, possible with gradient
a border is monochrome(gradient), is easily distinguishable from background, not barocco/rococco style
a picture is kind of real world picture with lots of details, not Malevich's Black Square
So we first search the picture and flatten background by entropy filtering
img=imread('http://i.stack.imgur.com/KMBRg.jpg');
%dimensions for neighbourhood are just a guess
cross_nhood = false(11,11); cross_nhood(:,6)=1;cross_nhood(6,:)=1;
img_ent = entropyfilt(img./255,repmat(cross_nhood,[1 1 3]));
img_ent_gray = rgb2gray(img_ent);
Then we find corners using Harris detector and choose 4 point: two leftmost and two rightmost, and crop the image thus removing background (with precision up to inclination). I use r2011a, you may have a bit different functions, refer to MATLAB help
harris_pts = corner(img_ent_gray);
corn_pts = sortrows(harris_pts,1);
corn_pts = [corn_pts(1:2,:);...
corn_pts(size(corn_pts,1)-1:size(corn_pts,1),:)];
crop_img=img(min(corn_pts(:,2)):max(corn_pts(:,2)),...
min(corn_pts(:,1)):max(corn_pts(:,1)),:);
corn_pts(:,1)=corn_pts(:,1) - min(corn_pts(:,1));
corn_pts(:,2)=corn_pts(:,2) - min(corn_pts(:,2));
corn_pts = corn_pts + 1;
An here is a problem: the lines between the corner points are inclined at a bit different angle. It can be both problem of corner detection and image capture (objective distortion and/or a bit wrong acquisition angle). There is no straightforward, always right solution. I'd better choose the biggest inclination (it will crop the picture a little) and start processing the image line by line (to split image use Bresenham algorithm) till any or most, you to choose, pixels belongs to the picture, not the inner border. The distinguishable feature can be local entropy, std of colour values, specific colour threshold, different methods of statistics, whatever.
Another approach is to do colour segmentation, I like most Gram-Shmidt orthogonalization or a*-b* color segmentation. However, you'll get all the same problems if image is skewed and part of a picture matches the colour of a border (see last picture, bottom left corner).

Detecting black spots on image - Image Segmentation

I'm trying to segment an image with Color-Based Segmentation Using K-Means Clustering. I already created 3 clusters, and the cluster number 3 is like this image:
This cluster has 3 different colors. And I want to only display the black spots of this image. How can I do that?
The image is 500x500x3 uint8.
Those "holes" look like they are well defined with the RGB values all being set to 0. To make things easy, convert the image to grayscale, then threshold the image so that any intensities less than 5 set the output to white. I use a threshold of 5 instead to ensure that we capture object pixels in their entirety taking variations into account.
Once that's done, you can use the function bwlabel from the image processing toolbox (I'm assuming you have it as you're dealing with images) where the second output tells you how many distinct white objects there are.
Something like this could work:
im = imread('http://i.stack.imgur.com/buW8C.png');
im_gray = rgb2gray(im);
holes = im_gray < 5;
[~,count] = bwlabel(holes);
I read in the image directly from StackOverflow, convert the image to grayscale, then determine a binary mask where any intensity that is less than 5, set the output to white or true. Once we have this image, we can use bwlabel's second output to determine how many objects there are.
I get this:
>> count
count =
78
As an illustration, if we show the image where the holes appear, I get this:
>> imshow(holes);
The amount of "holes" is a bit misleading though. If you specifically take a look at the bottom right of the image, there are some noisy pixels that don't belong to any of the "holes" so we should probably filter that out. As such, a simple morphological opening filter with a suitable sized structure will help remove spurious noisy islands. As such, use imopen combined with strel to define the structuring element (I'll choose a square) as well as a suitable size of the structuring element. After, use the structuring element and filter the resulting image and you can use this image to count the number of objects.
Something like this:
%// Code the same as before
im = imread('http://i.stack.imgur.com/buW8C.png');
im_gray = rgb2gray(im);
holes = im_gray < 5;
%// Further processing
se = strel('square', 5);
holes_process = imopen(holes, se);
%// Back to where we started
[~,count] = bwlabel(holes_process);
We get the following count of objects:
>> count
count =
62
This seems a bit more realistic. I get this image now instead:
>> imshow(holes_process);

Detect black dots from color background

My short question
How to detect the black dots in the following images? (I paste only one test image to make the question look compact. More images can be found →here←).
My long question
As shown above, the background color is roughly blue, and the dots color is "black". If pick one black pixel and measure its color in RGB, the value can be (0, 44, 65) or (14, 69, 89).... Therefore, we cannot set a range to tell the pixel is part of the black dot or the background.
I test 10 images of different colors, but I hope I can find a method to detect the black dots from more complicated background which may be made up of three or more colors, as long as human eyes can identify the black dots easily. Some extremely small or blur dots can be omitted.
Previous work
Last month, I have asked a similar question at stackoverflow, but have not got a perfect solution, some excellent answers though. Find more details about my work if you are interested.
Here are the methods I have tried:
Converting to grayscale or the brightness of image. The difficulty is that I can not find an adaptive threshold to do binarization. Obviously, turning a color image to grayscale or using the brightness (HSV) will lose much useful information. Otsu algorithm which calculates adaptive threshold can not work either.
Calculating RGB histogram. In my last question, natan's method is to estimate the black color by histogram. It is time-saving, but the adaptive threshold is also a problem.
Clustering. I have tried k-means clustering and found it quite effective for the background that only has one color. The shortage (see my own answer) is I need to set the number of clustering center in advance but I don't know how the background will be. What's more, it is too slow! My application is for real time capturing on iPhone and now it can process 7~8 frames per second using k-means (20 FPS is good I think).
Summary
I think not only similar colors but also adjacent pixels should be "clustered" or "merged" in order to extract the black dots. Please guide me a proper way to solve my problem. Any advice or algorithm will be appreciated. There is no free lunch but I hope a better trade-off between cost and accuracy.
I was able to get some pretty nice first pass results by converting to HSV color space with rgb2hsv, then using the Image Processing Toolbox functions imopen and imregionalmin on the value channel:
rgb = imread('6abIc.jpg');
hsv = rgb2hsv(rgb);
openimg = imopen(hsv(:, :, 3), strel('disk', 11));
mask = imregionalmin(openimg);
imshow(rgb);
hold on;
[r, c] = find(mask);
plot(c, r, 'r.');
And the resulting images (for the image in the question and one chosen from your link):
You can see a few false positives and missed dots, as well as some dots that are labeled with multiple points, but a few refinements (such as modifying the structure element used in the opening step) could clean these up some.
I was curios to test with my old 2d peak finder code on the images without any threshold or any color considerations, really crude don't you think?
im0=imread('Snap10.jpg');
im=(abs(255-im0));
d=rgb2gray(im);
filter=fspecial('gaussian',16,3.5);
p=FastPeakFind(d,0,filter);
imagesc(im0); hold on
plot(p(1:2:end),p(2:2:end),'r.')
The code I'm using is a simple 2D local maxima finder, there are some false positives, but all in all this captures most of the points with no duplication. The filter I was using was a 2d gaussian of width and std similar to a typical blob (the best would have been to get a matched filter for your problem).
A more sophisticated version that does treat the colors (rgb2hsv?) could improve this further...
Here is an extraodinarily simplified version, that can be extended to be full RGB, and it also does not use the image procesing library. Basically you can do 2-D convolution with a filter image (which is an example of the dot you are looking for), and from the points where the convolution returns the highest values, are the best matches for the dots. You can then of course threshold that. Here is a simple binary image example of just that.
%creating a dummy image with a bunch of small white crosses
im = zeros(100,100);
numPoints = 10;
% randomly chose the location to put those crosses
points = randperm(numel(im));
% keep only certain number of points
points = points(1:numPoints);
% get the row and columns (x,y)
[xVals,yVals] = ind2sub(size(im),points);
for ii = 1:numel(points)
x = xVals(ii);
y = yVals(ii);
try
% create the crosses, try statement is here to prevent index out of bounds
% not necessarily the best practice but whatever, it is only for demonstration
im(x,y) = 1;
im(x+1,y) = 1;
im(x-1,y) = 1;
im(x,y+1) = 1;
im(x,y-1) = 1;
catch err
end
end
% display the randomly generated image
imshow(im)
% create a simple cross filter
filter = [0,1,0;1,1,1;0,1,0];
figure; imshow(filter)
% perform convolution of the random image with the cross template
result = conv2(im,filter,'same');
% get the number of white pixels in filter
filSum = sum(filter(:));
% look for all points in the convolution results that matched identically to the filter
matches = find(result == filSum);
%validate all points found
sort(matches(:)) == sort(points(:))
% get x and y coordinate matches
[xMatch,yMatch] = ind2sub(size(im),matches);
I would highly suggest looking at the conv2 documentation on MATLAB's website.

matlab template matching only for 0 (or 1) in matrix

I'm a beginner in matlab programming and i'm having some troubles with template matching. I have a few white boxes with a black border (link for pic below) along with some text and I want to extract all the boxes,and there's also one that has an X in it(it's a multiple choice answer). In the beginning I used normxcorr2 , but the problem is that due to the many white pixels of the template, I get allot of irrelevant templates like only white spaces. I was looking for an algorithm that does template matching only on 0's , so I could get templates that have black squares only. I hope I made myself clear, thanks :)
http://i91.photobucket.com/albums/k284/Chris2401/sqrTemplate.png
EDIT: May 2nd, 2014
Now understanding what you are trying to achieve, I can now help you solve your problem. As you are a beginner to MATLAB, I will use a more simpler approach (even though there are more sophisticated methods to help you figure this out) as I want to demonstrate how the algorithm works.
You basically want to implement normxcorr2, but you only want to include pixels that are marked as black in the template. You also want to skip locations in the search image that are not black. In that case, I will layout the algorithm for you step by step, then write some code.
Read in the template and extract those pixel locations that are black
In a sliding window fashion, for each image patch of the same size as the template that is in the search image...
If the entire image patch is white, skip
Extract the same locations that are black
Compute the NCC using those locations only.
The output will be a map that contains the NCC for each location in the search image
I will not handle the case of the border pixels in the search image, as I am assuming that you want to be able to match something in the search image with the full size of the template.
Here are some assumptions. Let's assume the following variables:
imTemplate - The template image, such as the one that you have showed me
imSearch - The image we wish to search for when doing this NCC stuff
I am also assuming that each of the images are binary, as the title of your post has "0 or 1" in it.
As such, I have the following code for you:
[rowsSearch colsSearch] = size(imSearch); % Get dimensions of search image
[rowsTemp colsTemp] = size(imTemplate); % Get dimensions of template image
mapBlack = imSearch == 0; % Obtain a map of where the black pixels are in the template
numPixelsCompare = sum(mapBlack(:)); % Need to know how many pixels are valid for comparison
% Obtain area of searching within the search image
startSearchRows = 1 + floor(rowsSearch/2);
endSearchRows = rowsSearch - floor(rowsSearch/2);
startSearchCols = 1 + floor(colsSearch/2);
endSearchCols = colsSearch - floor(colsSearch/2);
% Need half the dimensions of each for the template dimensions... you will
% see why we need this later
rowsTempHalf = floor(rowsTemp/2);
colsTempHalf = floor(colsTemp/2);
% Where we will store our NCC calculations
NCCMap = zeros(rowsSearch, colsSearch);
% Because you want to include all of the black pixels in your
% calculations, and these are all the same, the signal you are comparing
% to basically consists of all white pixels.
% Create a vector that consists of all 1s that is the same size as how
% many black pixels exist in the template
compareSignal = ones(numPixelsCompare, 1);
% For each location in the search image (ensuring that the full template
% is inside the image)
for i = startSearchRows : endSearchRows
for j = startSearchCols : endSearchCols
% Grab an image patch that is the same size as the template
% At each location (i,j) this serves as the CENTRE of the image
% patch, and we are grabbing pixels surrounding this centre that
% will create a patch that is the same size as the template
searchBlock = imSearch(i-rowsTempHalf:i+rowsTempHalf, ...
j-colsTempHalf:j+colsTempHalf);
% If all of the pixels are white, skip
if (all(searchBlock == 1))
continue;
end
% Extract only those pixels that are valid in the template
searchPixels = searchBlock(mapBlock);
% Must invert so that black pixels become white
% You mentioned that white pixels are "background"
searchPixels = ~searchPixels;
% Compute NCC for this patch
NCCMap(i,j) = compareSignal'*searchPixels / ...
(sqrt(compareSignal'*compareSignal) * sqrt(searchPixels'*searchPixels));
end
end
If you're a bit confused with the way I calculated the NCC, it is basically what you're used to, but I used vector algebra to compute it instead. This should hopefully give you what you want. To find the best location of where the template matched, you can do the following to extract the row and column of this location:
[r,c] = find(NCCMap == max(NCCMap(:)));
I hope this solves your question. It is a tad inefficient, and it will really start to suffer with higher resolution images, but I wanted to give you a good start so you're not sitting around idle trying to figure it out on your own.
NB: I have not tested this code yet as I don't have an example search image that you are using to solve this problem. Hopefully this will work. Leave a comment and let me know how it goes.

Normalization of handwritten characters w.r.t size and position

I am doing a project on offline handwriting recognition.In the preprocessing stage,I need to normalize the handwritten part in binary image w.r.t its size and position.Can anyone tell me how to access just the writing part(black pixels) in the image and resize and shift its position?
Your problem is as broad as the field of image processing. There is no one way to segment an image into foreground and background so whatever solution you find here works on some cases and doesn't in others. However, the most basic way to segment a grayscale image is:
% invert your grayscale so text is white and background is black
gray_im = 1 - im2double(gray_im);
% compute the best global threshold
level = graythresh(gray_im);
% convert grayscale image to black and white based on best threshold
bw_im = im2bw(gray_im, level);
% find connected regions in the foreground
CC = bwconncomp(bw_im);
% if necessary, get the properties of those connected regions for further analysis
S = regionsprops(CC);
Note: Many people have much more sophisticated methods to segment and this is by no means the best way of doing it.
After post-processing, you will end up with one (or more) image containing only a single character. To resize to a specific size M x N, use:
resized_bw = imresize(single_char_im, [M N]);
To shift its position, the easiest way I know is to use circshift() function:
shifted_bw = circshift(resized_bw, [shift_pixels_up_down, shift_pixels_left_right]);
Note: circshift wraps the shifted columns or rows so if your bounding box is too tight, the best method is to pad your image, and re-crop it at the new location.

Resources