Implementing image histogram with Matlab
I am using tyring to implement (I know there is a custom function to achieve it) a histogram of a grayscale image 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 a function like: histogram_matlab('Harris.png')
I am getting some graph like:
which is obviously not what I expect, the x-axis should go from 0 to 255 and the y-axis from 0 to whatever maximum value is stored in histogram_values
.
I need to get something like what suggests imhist
:
How do I set it up? Am I doing a bad implementation?
Edit
I changed my code to the improvements and fixes 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 as expected:
It is noticeable here that there is some problem or error on the y-axis as it has definitely reached MORE than 2.
source to share
In terms of histogram computation, the frequency-to-intensity computation is correct, although there is a slight error ... more on that later. Also, I would personally avoid using loops here. See my little note at the end of this post.
However, there are three problems with your code:
Problem # 1 - Histogram is not initialized correctly
histogram_values
should contain your histogram, but you are initializing the histogram with a vector 0:255
. Each intensity value must start at 0 , so you really need to do this:
histogram_values = zeros(256,1);
Problem # 2 - A small error in the loop for
Your intensity ranges from 0 to 255, but MATLAB starts indexing at 1. If you ever get intensities equal to 0, you will get an out-of-bounds error. So the right thing to do is to take p
and add it at 1 to start indexing at 1. However, one complication I have to point out is that if you have a use case uint8
, adding 1 to intensity 255 will just saturate the value up to 255. It won't go to 256 .... so it's also reasonable that you roll something like this double
to ensure you reach 256.
Thus:
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 have to override the behavior histogram
and include the edges. Basically, do the following:
histogram(histogram_values, 0:255);
The second vector indicates where we should place the columns on the axis x
.
Small note
You can perform a complete histogram calculation without any loops for
. You can try this with a combination of bsxfun
, permute
, reshape
and two sum
:
mat = bsxfun(@eq, permute(0:255, [1 3 2]), im);
h = reshape(sum(sum(mat, 2), 1), 256, 1);
If you want a more detailed explanation of how this code works under the hood, see this conversation between kkuilla and me: http://chat.stackoverflow.com/rooms/81987/conversation/explanation-of-computing-an-images -histogram-vectorized
However, the gist of it is as shown below.
The first line of code creates a 1 column 3-D vector that ranges from 0 to 255 by permute
, and then using bsxfun
with the eq
(equal) function , we broadcast to get us a 3D matrix where each slice is the same size as and a grayscale image, and gives us locations that are equal to the intensity of interest. Specifically, the first snippet tells you where the elements are 0, the second slice tells you where the elements are 1 until the last snippet where it tells you where the elements are 255.
For the second line of code, once we have calculated this 3D matrix, we will calculate the two sums - first by summing each row independently, and then by summing each column of that intermediate result. We then get the total per slice, which tells us how many values there were for each intensity. It is therefore a 3D vector and so we reshape
will go back to a single 1D vector to complete the calculation.
To display a histogram I would use bar
with a flag histc
. Here's a reproducible example if we use an image cameraman.tif
:
%// 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 the following:
source to share
Your code looks correct. The problem is calling the histogram. You need to specify the number of bins in the call to the histogram, otherwise they will be calculated automatically.
Try this simple modification, which calls stem to get the correct graph, instead of relying on the 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 code review, you have error 0/1. If you have zero pixel 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
source to share