Select pixel position for image segmentation based on threshold value - matlab

I am writing a function to segment an image in MATLAB. I want to do a simple segmentation where I first sum up elements along columns and select pixel position which is greater than threshold and display only those pixels from the original[![enter image description here] image. I have written a function below where it can sum pixel intensities, I need help in selecting pixel position where intensity is greater than threshold and display only that part of image.
function S = image_segment(im)
% im is the image to segment
nrofsegments = 5; % there is 5 fragments for this image
S = cell(1,nrofsegments);
m = size(im,1);
n = size(im,2);
for kk = 1:nrofsegments
S_sum=sum(im); %sum the intensity along column
S_position = ... % get pixel position where the pixel intensity is greater than 128
S{kk} = ... % im(S_position), only part of image with this pixel position
end

This code should work for your image. Please find the explanations inline.
% Read your test image from the web
im = imread('https://i.stack.imgur.com/qg5gx.jpg');
% Get sum of intensities along the columns
colSum = sum(im);
% Set a proper threshold (128 was not working for me)
thresh = 300;
% Get an 1-D logical array indicating which columns have letters
hasLetter = colSum > thresh;
% Get the difference of consecutive values in hasLetter
hasLetterDiff = diff( hasLetter ) ;
% If this is greater than 0, we have the start of a letter.
% The find gets the indices where this is true.
% Then, we add one to compensate for the offset introduced by diff.
letterStartPos = find( hasLetterDiff > 0 ) + 1;
% Use a similar approach to find the letter end position
% Here, we search for differences smaller than 0.
letterEndPos = find( hasLetterDiff < 0 ) + 1;
% Cut out image from start and end positions
numSegments = 5;
S = cell(1, numSegments );
for k = 1 : numel(S)
S{k} = im( :, letterStartPos(k) : letterEndPos(k));
end
You should consider furthermore to add some code to make it fail-safe for cases where there are less than 5 segments. Moreover, I would recommend to do some low-pass or median filtering on colSum.
Alternative: Instead of deriving the start and stop positions, it would also be possible to use splitapply (introduced in R2015) directly on hasLetterDiff as follows:
S = splitapply( #(X) { im(:,X) }, 1 : numel( hasLetter), [1 cumsum( abs( hasLetterDiff))] + 1);
S = S(2:2:end);

Related

Why is my bilinear interpolation vastly different from the in-built matlab function?

I've been working on bilinear interpolation based on wiki example in matlab. I followed the example to the T, but when comparing the outputs from my function and the in-built matlab function, the results are vastly different and I can't figure out why or how that happens.
Using inbuilt matlab function:
Result of my function below:
function T = bilinear(X,h,w)
%pre-allocating the output size
T = uint8(zeros(h,w));
%padding the original image with 0 so i don't go out of bounds
X = padarray(X,[2,2],'both');
%calculating dimension ratios
hr = h/size(X,1);
wr = w/size(X,2);
for row = 3:h-3
for col = 3:w-3
%for calculating equivalent position on the original image
o_row = ceil(row/hr);
o_col = ceil(col/wr);
%getting the intensity values from horizontal neighbors
Q12=X(o_row+1,o_col-1);
Q22=X(o_row+1,o_col+1);
Q11=X(o_row-1,o_col-1);
Q21=X(o_row-1,o_col+1);
%calculating the relative positions to the enlarged image
y2=round((o_row-1)*hr);
y=round(o_row*hr);
y1=round((o_row+1)*hr);
x1=round((o_col-1)*wr);
x=round(o_col*wr);
x2=round((o_col+1)*wr);
%interpolating on 2 first axis and the result between them
R1=((x2-x)/(x2-x1))*Q11+((x-x1)/(x2-x1))*Q21;
R2=((x2-x)/(x2-x1))*Q12+((x-x1)/(x2-x1))*Q22;
P=round(((y2-y)/(y2-y1))*R1+((y-y1)/(y2-y1))*R2);
T(row,col) = P;
T = uint8(T);
end
end
end
The arguments passed to the function are step4 = bilinear(Igray,1668,1836); (scale factor of 3).
You are finding the pixel nearest to the point you want to interpolate, then find 4 of this pixel’s neighbors and interpolate between them:
o_row = ceil(row/hr);
o_col = ceil(col/wr);
Q12=X(o_row+1,o_col-1);
Q22=X(o_row+1,o_col+1);
Q11=X(o_row-1,o_col-1);
Q21=X(o_row-1,o_col+1);
Instead, find the 4 pixels nearest the point you want to interpolate:
o_row = ceil(row/hr);
o_col = ceil(col/wr);
Q12=X(o_row,o_col-1);
Q22=X(o_row,o_col);
Q11=X(o_row-1,o_col-1);
Q21=X(o_row-1,o_col);
The same pixel’s coordinates then need to be used when computing distances. The easiest way to do that is to separate out the floating-point coordinates of the output pixel ((row,col)) in the input image (o_row,o_col), and the location of the nearest pixel in the input image (fo_row,fo_col). Then, the distances are simply d_row = o_row - fo_row and 1-d_row, etc.
This is how I would write this function:
function T = bilinear(X,h,w)
% Pre-allocating the output size
T = zeros(h,w,'uint8'); % Create the matrix in the right type, rather than cast !!
% Calculating dimension ratios
hr = h/size(X,1); % Not with the padded sizes!!
wr = w/size(X,2);
% Padding the original image with 0 so I don't go out of bounds
pad = 2;
X = padarray(X,[pad,pad],'both');
% Loop
for col = 1:w % Looping over the row in the inner loop is faster!!
for row = 1:h
% For calculating equivalent position on the original image
o_row = row/hr;
o_col = col/wr;
fo_row = floor(o_row); % Code is simpler when using floor here !!
fo_col = floor(o_col);
% Getting the intensity values from horizontal neighbors
Q11 = double(X(fo_row +pad, fo_col +pad)); % Indexing taking padding into account !!
Q21 = double(X(fo_row+1+pad, fo_col +pad)); % Casting to double might not be necessary, but MATLAB does weird things with integer computation !!
Q12 = double(X(fo_row +pad, fo_col+1+pad));
Q22 = double(X(fo_row+1+pad, fo_col+1+pad));
% Calculating the relative positions to the enlarged image
d_row = o_row - fo_row;
d_col = o_col - fo_col;
% Interpolating on 2 first axis and the result between them
R1 = (1-d_row)*Q11 + d_row*Q21;
R2 = (1-d_row)*Q12 + d_row*Q22;
T(row,col) = round((1-d_col)*R1 + d_col*R2);
end
end
end

How to implement median filter with a kernel in MATLAB to smooth an image?

I don't know how to implement a median filter with a sliding window (kernel) in MATLAB. I need to know how the implementation looks like so I can try to implement a BSE algorithm (block smart erase), which is very similar to the median filter. I need that to erase some black and white pixels. I know that there's the medfilt2() function but I need to know how it is implemented.
The BSE algorithm works like this:
The BSE algorithm is based on median technology and takes the place of the extreme value (black or white) pixel by the median value of their surrounding pixels.
1) For an NxN window centered at the test pixel, where N would normally be and larger value should be suggested.
2) If f(i,j) = 0 or f(i,j) = 255, f(i,j) is an absolute extreme value pixel that must be estimated; go to step 3. Otherwise, the value of f(i,j) is not altered; go to step 4.
3) When an extreme value pixel is detected, its gray level is substituted by the median value of the window.
4) The procedure is repeated for the next window.
What I understood was that I need to implement the median filter with a condition to check if the current pixel value is 0 or 255. If it is, I change that value for the median value of its neighbourhood pixels.
I dont know if I was clear enough but I need some help with that:).
Current BSE algorithm:
function [outimg] = medianfilt(img,sz)
green = img(:,:,2);
[rows,cols] = size(green); % get size of grayscale image
pad = sz-1; % padding to be added
nimg = zeros(rows+pad,cols+pad); % padded image
nimg(pad/2+1:rows+pad/2, pad/2+1:cols+pad/2) = green;
outimg = zeros(rows,cols); % output / median filtered image
for x = pad/2 + 1 : cols + pad/2 % loop over columns
for y = pad/2 + 1 : rows + pad/2 % loop over rows
if nimg(y,x) == 0 || nimg(y,x) == 255
win = nimg(y-pad/2:y+pad/2, x-pad/2:x+pad/2); % get mxm window
outimg(y-pad/2,x-pad/2) = median(win(:)); % find median
else
outimg(y-pad/2,x-pad/2) = nimg(y,x);
end
win = nimg(y-pad/2:y+pad/2, x-pad/2:x+pad/2); % get mxm window
outimg(y-pad/2,x-pad/2) = median(win(:)); % find median
end
end
imshow(outimg);
end
you can implement median filter as below:
function [outimg] = medianfilt(img,sz)
[rows,cols] = size(img); % get size of grayscale image
pad = sz-1; % padding to be added
nimg = zeros(rows+pad,cols+pad); % padded image
nimg(pad/2+1:rows+pad/2, pad/2+1:cols+pad/2) = img;
outimg = zeros(rows,cols); % output / median filtered image
for x = pad/2 + 1 : cols + pad/2 % loop over columns
for y = pad/2 + 1 : rows + pad/2 % loop over rows
win = nimg(y-pad/2:y+pad/2, x-pad/2:x+pad/2); % get mxm window
outimg(y-pad/2,x-pad/2) = median(win(:)); % find median
end
end
end
you can check for BSE as below:
if nimg(y,x) == 0 || nimg(y,x) == 255
win = nimg(y-pad/2:y+pad/2, x-pad/2:x+pad/2); % get mxm window
outimg(y-pad/2,x-pad/2) = median(win(:)); % find median
else
outimg(y-pad/2,x-pad/2) = nimg(y,x);
end
As you noted, MATLAB has medfilt2. You can use this function to build your filter:
img = imread(...);
med = medfilt2(img);
mask = img==0 | img==255;
img(mask) = med(mask);
What the code above does is
compute the median filtered image med,
identify the pixels that need to be replaced (pixels with a value of 0 or 255), leading to a logical array mask that can be used for indexing, and finally
replace the given pixels in the input image with the corresponding pixels from the median-filtered image.

selecting line using hough transform

Problem: Find an unwanted line in an image using Hough transform.
I have done the following,
Apply directional filter to analyze 12 different directions, rotated with respect to 15° each other.
Apply thresholding to obtain 12 binary images.
Now, I need to select either of those two images marked in yellow. Coz, the lines in those two images are the most prominent.
I have tried the following code. It doesn't seem to be working.
MATLAB Code
% Read 12 images into workspace.
input_images = {imread('1.png'),imread('2.png'),imread('3.png'),...
imread('4.png'),imread('5.png'),imread('6.png'),...
imread('7.png'),imread('8.png'),imread('9.png'),...
imread('10.png'),imread('11.png'),imread('12.png')};
longest_line = struct('point1',[0 0], 'point2',[0 0], 'theta', 0, 'rho', 0);
for n=1:12
%Create a binary image.
binary_image = edge(input_images{n},'canny');
%Create the Hough transform using the binary image.
[H,T,R] = hough(binary_image);
%Find peaks in the Hough transform of the image.
P = houghpeaks(H,3,'threshold',ceil(0.3*max(H(:))));
%Find lines
hough_lines = houghlines(binary_image,T,R,P,'FillGap',5,'MinLength',7);
longest_line = FindTheLongestLine(hough_lines, longest_line);
end
% Highlight the longest line segment by coloring it cyan.
plot(longest_line.point1, longest_line.point2,'LineWidth',2,'Color','cyan');
.
Relevant Source Code
function longest_line = FindTheLongestLine( hough_lines , old_longest_line)
%FINDTHELONGESTLINE Summary of this function goes here
% Detailed explanation goes here
longest_line = struct('point1',[0 0] ,'point2',[0 0],'theta', 0, 'rho', 0);
max_len = 0;
N = length(hough_lines);
for i = 1:N
% Determine the endpoints of the longest line segment
len = LenthOfLine(hough_lines(i));
if ( len > max_len)
max_len = len;
longest_line = hough_lines(i);
end
end
old_len = LenthOfLine(old_longest_line);
new_len = LenthOfLine(longest_line);
if(old_len > new_len)
longest_line = old_longest_line;
end
end
function length = LenthOfLine( linex )
%LENTHOFLINE Summary of this function goes here
% Detailed explanation goes here
length = norm(linex.point1 - linex.point2);
end
Test Images
Here are the 12 images, drive.google.com/open?id=0B-2FDw63ZNTnRnEzYlNyS0V4YVE
The problem with your code is the FillGap property of houghlines. You should allow larger gaps in the returned lines, because the searched line does not need to be continuous, e.g. 500:
hough_lines = houghlines(binary_image,T,R,P,'FillGap',500,'MinLength',7);
This finds the largest line in image 7 as desired.
Visualisation
To plot the found line on top of the image, you can use the following code:
figure
imshow(input_images{7});
hold on
% Highlight the longest line segment by coloring it cyan.
plot([longest_line.point1(1) longest_line.point2(1)], [longest_line.point1(2) longest_line.point2(2)],'LineWidth',2,'Color','cyan');
Finding maximum peak in Hough Transform
As an alternative, you may consider to select the line corresponding to the largest Hough Transform value, instead of the longest line. This can be done by selecting the longest_line as follows:
longest_line = ...
largest_H = 0; % init value
for n=1:12
binary_image = edge(input_images{n},'canny');
[H,T,R] = hough(binary_image);
P = houghpeaks(H,1,'threshold',ceil(0.3*max(H(:))));
hough_lines = houghlines(binary_image,T,R,P,'FillGap',500,'MinLength',7);
if (largest_H < H(P(1, 1), P(1, 2)))
largest_H = H(P(1, 1), P(1, 2));
longest_line = hough_lines(1);
longest_line.image = n;
end
end
This selects the following line in image 6, which is the other allowed outcome:
you can try changing the Hough functions' parameters according to your specific problem, it's not a perfect solution but it may be good enough for you:
img = im2double(rgb2gray(imread('line.jpg')));
% edge image
BW = edge(img,'canny');
% relevant angles (degrees) interval for the line you want
thetaInterval = -80:-70;
% run hough transform and take single peak
[H,T,R] = hough(BW,'Theta',thetaInterval);
npeaks = 1;
P = houghpeaks(H,npeaks);
% generate lines
minLen = 150; % you want the long line which is ~250 pixels long
% merge smaller lines (same direction) within gaps of 30 pixels
fillGap = 30;
lines = houghlines(BW,T,R,P,'FillGap',fillGap,'MinLength',minLen );
% plot
imshow(img);
hold on
xy = [lines.point1; lines.point2];
plot(xy(:,1),xy(:,2),'g','LineWidth',2);

Bandpass Filter for 4D image in Matlab

I have implemented in Matlab a bandpass filter for a 4D image (4D matrix). The first three dimensions are spatial dimensions, the last dimension is a temporal one. Here is the code:
function bandpass_img = bandpass_filter(img)
% Does bandpass filtering on input image
%
% Input:
% img: 4D image
%
% Output:
% bandpass_img: Bandpass filtered image
TR = 1; % Repetition time
n_vols = size(img,3);
X = [];
% Create matrix (voxels x time points)
for z = 1:size(img,3)
for y = 1:size(img,2)
for x = 1:size(img,1)
X = [X; squeeze(img(x,y,z,:))']; %#ok<AGROW>
end
end
end
Fs = 1/TR;
nyquist = 0.5*Fs;
% Pass bands
F = [0.01/nyquist, 0.1/nyquist];
type = 'bandpass';
% Filter order
n = floor(n_vols/3.5);
% Ensure filter order is odd for bandpass
if (mod(n,2) ~= 0), n=n+1; end
fltr = fir1(n, F, type);
% Looking at frequency response
% freqz(fltr)
% Store plot to file
% set(gcf, 'Color', 'w');
% export_fig('freq_response', '-png', '-r100');
% Apply to image
X = filter(fltr, 1, X);
% Reconstructing image
i = 1;
bandpass_img = zeros(size(img));
for z = 1:size(img,3)
for y = 1:size(img,2)
for x = 1:size(img,1)
bandpass_img(x,y,z,:) = X(i,:)';
i = i + 1;
end
end
end
end
I'm not sure if the implementation is correct. Could somebody verify it or does somebody find a failure?
Edit: Thanks to SleuthEye it now kind of works when I'm using bandpass_img = filter(fltr, 1, img, [], 4);. But there is still a minor problem. My images are of size 80x35x12x350, i.e. there are 350 time points. I have plotted the average time series before and after applying the bandpass filter.
Before bandpass filtering:
After bandpass filtering:
Why is this peak at the very beginning of the filtered image?
Edit 2: There is now a peak at the beginning and at the end. See:
I have made a second plot where I marked each point with a *. See:
So the first and last two time points seem to be lower.
It seems that I have to remove 2 time points at the beginning and also 2 time points at the end, so in total I will loose 4 time points.
What do you think?
Filtering a concatenation of all the elements using the 1-D filter function as you are doing is likely going to result in a distorted image as the smoothing makes the end of each row blend into the beginning of the next one. Unless you are trying to obtain a psychedelic rendition of your 4D data, this is unlikely to do what you are expecting it to.
Now, according to Matlab's filter documentation:
y = filter(b,a,x,zi,dim) acts along dimension dim. For example, if x is a matrix, then filter(b,a,x,zi,2) returns the filtered data for each row.
So, to smooth out the 3D images over time (which you indicated is the fourth dimension of you matrix img), you should use
bandpass_img = filter(fltr, 1, img, [], 4);
Used as such, the signal would starts a 0 because that's the default initial state of the filter and the filter takes a few samples to ramp up. If you know the value of the initial state you can specify that with the zi argument (the 4th argument):
bandpass_img = filter(fltr, 1, img, zi, 4);
Otherwise, a typical order N linear FIR filter has a delay of N/2 samples. To get rid of the initial ramp up you can thus just discard those N/2 initial samples:
bandpass_img = bandpass_img(:,:,:,(N/2+1):end);
Similarly, if you want to see the output corresponding to the last N/2 input values, you'd have to pad your input with N/2 extra samples (zeros will do):
img = padarray(img, [0 0 0 N/2], 0, 'post');

Image histogram implementation with Matlab

I'm tyring to implement (I know there's a custom function for achieving it) the grayscale image histogram in Matlab, so far I've tried:
function h = histogram_matlab(imageSource)
openImage = rgb2gray(imread(imageSource));
[rows,cols] = size(openImage);
histogram_values = [0:255];
for i = 1:rows
for j = 1:cols
p = openImage(i,j);
histogram_values(p) = histogram_values(p) + 1;
end
end
histogram(histogram_values)
However when I call the function, for example: histogram_matlab('Harris.png')
I obtain some graph like:
which is obviously not what I expect, x axis should go from 0 to 255 and y axis from 0 to whatever max value is stored in histogram_values.
I need to obtain something like what imhist offers:
How should I set it up? Am I doing a bad implementation?
Edit
I've changed my code to improvements and corrections suggested by #rayryeng:
function h = histogram_matlab(imageSource)
openImage = rgb2gray(imread(imageSource));
[rows,cols] = size(openImage);
histogram_values = zeros(256,1)
for i = 1:rows
for j = 1:cols
p = double(openImage(i,j)) +1;
histogram_values(p) = histogram_values(p) + 1;
end
end
histogram(histogram_values, 0:255)
However the histogram plot is not that expected:
Here it's noticeable that there's some issue or error on y axis as it definitely would reach MORE than 2.
In terms of calculating the histogram, the computation of the frequency per intensity is correct though there is a slight error... more on that later. Also, I would personally avoid using loops here. See my small note at the end of this post.
Nevertheless, there are three problems with your code:
Problem #1 - Histogram is not initialized properly
histogram_values should contain your histogram, yet you are initializing the histogram by a vector of 0:255. Each intensity value should start with a count of 0, and so you actually need to do this:
histogram_values = zeros(256,1);
Problem #2 - Slight error in for loop
Your intensities range from 0 to 255, yet MATLAB starts indexing at 1. If you ever get intensities that are 0, you will get an out-of-bounds error. As such, the proper thing to do is to take p and add it with 1 so that you start indexing at 1. However, one intricacy I need to point out is that if you have a uint8 precision image, adding 1 to an intensity of 255 will simply saturate the value to 255. It won't go to 256.... so it's also prudent that you cast to something like double to ensure that 256 will be reached.
Therefore:
histogram_values = zeros(256,1);
for i = 1:rows
for j = 1:cols
p = double(openImage(i,j)) + 1;
histogram_values(p) = histogram_values(p) + 1;
end
end
Problem #3 - Not calling histogram right
You should override the behaviour of histogram and include the edges. Basically, do this:
histogram(histogram_values, 0:255);
The second vector specifies where we should place bars on the x-axis.
Small note
You can totally implement the histogram computation yourself without any for loops. You can try this with a combination of bsxfun, permute, reshape and two sum calls:
mat = bsxfun(#eq, permute(0:255, [1 3 2]), im);
h = reshape(sum(sum(mat, 2), 1), 256, 1);
If you'd like a more detailed explanation of how this code works under the hood, see this conversation between kkuilla and myself: https://chat.stackoverflow.com/rooms/81987/conversation/explanation-of-computing-an-images-histogram-vectorized
However, the gist of it as below.
The first line of code creates a 3D vector of 1 column that ranges from 0 to 255 by permute, and then using bsxfun with the eq (equals) function, we use broadcasting so that we get a 3D matrix where each slice is the same size as the grayscale image and gives us locations that are equal to an intensity of interest. Specifically, the first slice tells you where elements are equal to 0, the second slice tells you where elements are equal to 1 up until the last slice where it tells you where elements are equal to 255.
For the second line of code, once we compute this 3D matrix, we compute two sums - first summing each row independently, then summing each column of this intermediate result. We then get the total sum per slice which tells us how many values there were for each intensity. This is consequently a 3D vector, and so we reshape this back into a single 1D vector to finish the computation.
In order to display a histogram, I would use bar with the histc flag. Here's a reproducible example if we use the cameraman.tif image:
%// Read in grayscale image
openImage = imread('cameraman.tif');
[rows,cols] = size(openImage);
%// Your code corrected
histogram_values = zeros(256,1);
for i = 1:rows
for j = 1:cols
p = double(openImage(i,j)) + 1;
histogram_values(p) = histogram_values(p) + 1;
end
end
%// Show histogram
bar(0:255, histogram_values, 'histc');
We get this:
Your code looks correct. The problem is with the call to histogram. You need to supply the number of bins in the call to histogram, otherwise they will be computed automatically.
Try this simple modification which calls stem to get the right plot, instead of relying on histogram
function h = histogram_matlab(imageSource)
openImage = rgb2gray(imread(imageSource));
[rows,cols] = size(openImage);
histogram_values = [0:255];
for i = 1:rows
for j = 1:cols
p = openImage(i,j);
histogram_values(p) = histogram_values(p) + 1;
end
end
stem(histogram_values); axis tight;
EDIT: After some inspection of the code you have a 0/1 error. If you have a pixel of value zero then histogram_value(p) will give you an index error
Try this instead. No need for vectorization for this simple case:
function hv = histogram_matlab_vec(I)
assert(isa(I,'uint8')); % for now we assume uint8 with range [0, 255]
hv = zeros(1,256);
for i = 1 : numel(I)
p = I(i);
hv(p + 1) = hv(p + 1) + 1;
end
stem(hv); axis tight;
end