Histogram matching of two Images without using histeq - image

It is well known that histeq in MATLAB can perform histogram matching so that an image's histogram is transformed to look like another histogram. I am trying to perform this same operation without using histeq. I'm aware that you need to calculate the CDFs between the two images, but I'm not sure what to do next. What do I do?

Histogram matching is concerned with transforming one image's histogram so that it looks like another. The basic principle is to compute the histogram of each image individually, then compute their discrete cumulative distribution functions (CDFs). Let's denote the CDF of first image as while the CDF of the second image is . Therefore, would denote what the CDF value is for intensity x for the first image.
Once you calculate the CDFs for each of the images, you need to compute a mapping that transforms one intensity from the first image so that it is in agreement with the intensity distribution of the second image. To do this, for each intensity in the first image - let's call this which will be from [0,255] assuming an 8-bit image - we must find an intensity in the second image (also in the range of [0,255]) such that:
There may be a case where we won't get exactly an equality, so what you would need to do is find the smallest absolute difference between and . In other words, for a mapping M, for each entry of , we must find an intensity such that:
You would do this for all 256 values, and we would produce a mapping. Once you find this mapping, you simply have to apply this mapping on the first image to get it to look like the intensity distribution of the second image. A rough (and perhaps inefficient) algorithm would look something like this. Let im1 be the first image (of type uint8) while im2 is the second image (of type uint8):
M = zeros(256,1,'uint8'); %// Store mapping - Cast to uint8 to respect data type
hist1 = imhist(im1); %// Compute histograms
hist2 = imhist(im2);
cdf1 = cumsum(hist1) / numel(im1); %// Compute CDFs
cdf2 = cumsum(hist2) / numel(im2);
%// Compute the mapping
for idx = 1 : 256
[~,ind] = min(abs(cdf1(idx) - cdf2));
M(idx) = ind-1;
end
%// Now apply the mapping to get first image to make
%// the image look like the distribution of the second image
out = M(double(im1)+1);
out should contain your matched image where it transforms the intensity distribution of the first image to match that of the second image. Take special care of the out statement. The intensity range of im1 spans between [0,255], but MATLAB's indexing for arrays starts at 1. Therefore, we need to add 1 to every value of im1 so we can properly index into M to produce our output. However, im1 is of type uint8, and MATLAB saturates values should you try and go beyond 255. As such, to ensure that we get to 256, we must cast to a data type that is beyond 8-bit precision. I decided to use double, then when we add 1 to every value in im1, we will span between 1 to 256 so we can properly index into M. Also take not that when I find the location that minimizes the difference, I also must subtract by 1 as the data type spans from [0,255].

Related

A proper way to convert 2D Array into RGB or GrayScale image for precision difference

I have a 2D CNN model where I perform a classification task. My images are all coming from a sensor data after conversion.
So, normally, my way is to convert them into images using the following approach
newsize = (9, 1000)
pic = acc_normalized[0]
img = Image.fromarray(np.uint8(pic*255), 'L')
img = img.resize(newsize)
image_path = "Images_Accel"
image_name = "D1." + str(2)
img.save(f"{image_path}/{image_name}.jpeg")
This is what I obtain:
However, their precision is sort of important. For instance, some of the numerical values are like:
117.79348187327987 or 117.76568758022673.
As you see in the above line, their difference is the digits, when I use uint8, it only takes 117 to when converting it into image pixels and it looks the same, right? But, I'd like to make them different. In some cases, the difference is even at the 8th or 10th digit.
So, when I try to use mode F and save them .jpeg in Image.fromarray line it gives me error and says that PIL cannot write mode F to jpeg.
Then, I tried to first convert them RGB like following;
img = Image.fromarray(pic, 'RGB')
I am not including np.float32 just before pic or not multiplying it by 255 as it is. Then, I convert this image to grayscale. This is what I got for RGB image;
After converting RGB into grayscale:
As you see, it seems that there is a critical different between the first pic and the last pic. So, what should be the proper way to use them in 2D CNN classification? or, should I convert them into RGB and choose grayscale in CNN implementation and a channel of 1? My image dimensions 1000x9. I can even change this dimension like 250x36 or 100x90. It doesn't matter too much. By the way, in the CNN network, I am able to get more than 90% test accuracy when I use the first-type of image.
The main problem here is using which image conversion method I'll be able to take into account those precision differences across the pixels. Would you give me some idea?
---- EDIT -----
Using .tiff format I made some quick comparisons.
First of all, my data looks like the following;
So, if I convert this first reading into an image using the following code where I use np.float64 and L gives me a grayscale image;
newsize = (9, 1000)
pic = acc_normalized[0]
img = Image.fromarray(np.float64(pic), 'L')
img = img.resize(newsize)
image_path = "Images_Accel"
image_name = "D1." + str(2)
img.save(f"{image_path}/{image_name}.tiff")
It gives me this image;
Then, the first 15x9 matrix seems like following image; The contradiction is that if you take a closer look at the numerical array, for instance (1,4) member, it's a complete black where the numerical array is equal to 0.4326132099074307. For grayscale images, black means that it's close to 0 cause it makes white if it's close to 1. However, if it's making a row operation, there is another value closer to 0 and I was expecting to see it black at (1,5) location. If it does a column operation, there is again something wrong. As I said, this data has been already normalized and varies within 0 and 1. So, what's the logic that it converts the array into an image? What kind of operation it does?
Secondly, if I first get an RGB image of the data and then convert it into a grayscale image, why I am not having exactly the same image as what I obtained first? Should the image coming from direct grayscale conversion (L method, np.float64) and the one coming from RGB-based (first I get RGB then convert it to grayscale) be the same? There is a difference in black-white pixels in those images. I do not know why we have it.
---- EDIT 2 ----
.tiff image with F mode and np.float32 gives the following;
I don't really understand your question, but you seem to want to store image differences that are less than 1, i.e. less than the resolution of integer values.
To do so, you need to use an image format that can store floats. JPEG, PNG, GIF, TGA and BMP cannot store floats. Instead, use TIFF, EXR or PFM formats which can handle floats.
Alternatively, you can create 16-bit PNG images wherein each pixel can store values in range 0..65535. So, say the maximum difference you wanted to store was 60 you could calculate the difference and multiply it by 1000 and round it to make an integer in range 0..60000 and store as 16-bit PNG.
You could record the scale factor as a comment within the image if it is variable.

what is the difference between image vs imagesc in matlab

I want to know the difference between imagesc & image in matlab
I used this example to try to figure out the difference beween the two but i couldn't explain the difference in the output images by myself; could you help me with that ?
I = rand(256,256);
for i=1:256
for j=1:256
I(i,j) = j;
end
end
figure('Name','Comparison between image et imagesc')
subplot(2,1,1);image(I);title('using image(I)');
subplot(2,1,2);imagesc(I);title('using imagesc(I)');
figure('Name','gray level of image');
image(I);colormap('gray');
figure('Name','gray level of imagesc');
imagesc(I);colormap('gray');
image displays the input array as an image. When that input is a matrix, by default image has the CDataMapping property set to 'direct'. This means that each value of the input is interpreted directly as an index to a color in the colormap, and out of range values are clipped:
image(C) [...] When C is a 2-dimensional MxN matrix, the elements of C are used as indices into the current colormap to determine the color. The
value of the image object's CDataMapping property determines the
method used to select a colormap entry. For 'direct' CDataMapping (the default), values in C are treated as colormap indices (1-based if double, 0-based if uint8 or uint16).
Since Matlab colormaps have 64 colors by default, in your case this has the effect that values above 64 are clipped. This is what you see in your image graphs.
Specifically, in the first figure the colormap is the default parula with 64 colors; and in the second figure colormap('gray') applies a gray colormap of 64 gray levels. If you try for example colormap(gray(256)) in this figure the image range will match the number of colors, and you'll get the same result as with imagesc.
imagesc is like image but applying automatic scaling, so that the image range spans the full colormap:
imagesc(...) is the same as image(...) except the data is scaled to use the full colormap.
Specifically, imagesc corresponds to image with the CDataMapping property set to 'scaled':
image(C) [...] For 'scaled' CDataMapping, values in C are first scaled according to the axes CLim and then the result is treated as a colormap index.
That's why you don't see any clipping with imagesc.

How to ignore certain values in A histogram? without using NaN in Matlab?

Say I have an grey scale image S and I'm looking to ignore all values above 250, how do I do it with out using NaN? the reason I don't want to use NaN is because Im looking to take statistical information from the resultant image such as average etc.
You can collect all image pixel intensities that are less than 250. That's effectively performing the same thing. If your image was stored in A, you can simply do:
pix = A(A < 250);
pix will be a single vector of all image pixels in A that have intensity of 249 or less. From there, you can perform whatever operations you want, such as the average, standard deviation, calculating the histogram of the above, etc.
Going with your post title, we can calculate the histogram of an image very easily using imhist that's part of the image processing toolbox, and so:
out = imhist(pix);
This will give you a 256 element vector where each value denotes the intensity count for a particular intensity. If we did this properly, you should only see bin counts up to intensity 249 (location 250 in the vector) and you should. If you don't have the image processing toolbox, you can repeat the same thing using histc and manually specifying the bin cutoffs to go from 0 up to 249:
out = histc(pix, 0:249);
The difference here is that we will get a histogram of exactly 250 bins whereas imhist will give you 256 bins by default. However, histc is soon to be deprecated and histcounts is what is recommended to use. Still the same syntax:
out = histcounts(pix, 0:249);
You can use logical indexing to build a histogram only using values in your specified range. For example you might do something like:
histogram(imgData(imgData < 250))

finding no.of occurrences of each and every pixel in image using matlab

How to find the no. of occurrences of each pixel in an RGB image (i.e each color)??.
I need number of occurrences of every pixel of the image stored corresponding to their coordinates...
Try:
maxNoOfColors=1024;
[x,map]=rgb2ind(image,maxNoOfColors);
[counts, histmap] = imhist(x,map);
This will give you a histogram(how many times occurs) for each distinct color in you image. If you think there are more colors in your image you can make the number 1024 larger, works up to 65k colors, after that it works with bins. You can use the map to go from an rgb value to an histogram value.

Determine a color “how much of a single color is in the image”

I’m trying to calculate an average value of one color over the whole image in order to determine how color, saturation or intencity or eny other value describing this changes between frmaes of the video.
However i would like to get just one value that will describe whole frame (and sigle, chosen color in it). Calculating simple average value of color in frame gives me very small differences between video frames, just 2-3 on a 0..255 space.
Is there any other method to determine color of the image other than histogram which as i understand will give me more than one value describing single frame.
Which library are you using for image processing? If it's OpenCV (or Matlab) then the steps here will be quite easy. Otherwise you'd need to look around and experiment a bit.
Use a Mean Shift filter on RGB (or gray, whichever) to cluster the colors in the image - nearly similar colors are clustered together. This lessens the number of colors to deal with.
Change to gray-level and compute a frequency histogram with bins [0...255] of pixel values that are present in the image
The highest frequency - the median - will correspond to the bin (color) that is present the most. The frequency of each bin will give you the no. of pixels of the color that is present in the frame.
Take the median value as the single color to describe your frame - the color present in the largest amount in the frame.
The key point here is if the above steps are fast enough for realtime video. You'd have to try to find out I guess.
Worst case scenario, you could loop over all the pixels in the image and do a count. Not sure what you are using programming wise but I use Python with Numpy something similar to this. Where pb is a gtk pixbuf with my image in it.
def pull_color_out(self, pb, *args):
counter = 0
dat = pb.get_pixels_array().copy()
for y in range(0,pb.get_width()):
for x in range(0,pb.get_height()):
p = dat[x][y]
#counts pure red pixels
if p[1] = 255 and p[2] = 0 and p[3] = 0:
counter += 1
return counter
Other than that, I would normally use a histogram and get the data I need from that. Mainly, this will not be your fastest option, especially for a video, but if you have time or just a few frames then hack away :P

Resources