I want to determine the colour of an object in an image. I was able to determine the mask of the object and generated the HSV histogram of the image using the mask.
cvtColor( Frame, hsv_base, CV_BGR2HSV );
int h_bins = 50;
int s_bins = 32;
int v_bins = 10;
int histSize[] = { h_bins, s_bins, v_bins };
float h_ranges[] = { 0, 180 };
float s_ranges[] = { 0, 256 };
float v_ranges[] = { 0, 256 };
const float* ranges[] = { h_ranges, s_ranges, v_ranges };
int channels[] = { 0, 1, 2};
calcHist( &hsv_base, 1, channels, mask, hist_base, 3, histSize, ranges, true, false ); //mask is the mask of the object
Everybody shows a different method to do so.Can anyone tell me a simple method to determine the colour from the histogram?
When plotting the histogram, the X-axis serves as our “bins”. If we construct a histogram with 256 bins, then we are effectively counting the number of times each pixel value occurs.
In contrast, if we use only 2 (equally spaced) bins, then we are counting the number of times a pixel is in the range [0, 128) or [128, 255]. The number of pixels binned to the X-axis value is then plotted on the Y-axis.
Therefore, if you want to get the most common colour you need 256 bins (for a 256 bit image) and count the number of entries.
Related
i have this part of code. im beginner and want to understand this code. can someone explain me what happens if i set cumulative to true. Where is the difference to false. Would be nice if someone could explain me the difference.
i just see that the output is difference but i dont know why
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <opencv2/imgproc.hpp>
using namespace std;
using namespace cv;
cv::Mat plotHistogram(cv::Mat &image, bool cumulative = false, int histSize = 256);
int main()
{
cv::Mat img = cv::imread("\\schrott.png"); // Read the file
if (img.empty()) // Check for invalid input
{
std::cout << "Could not open or find the frame" << std::endl;
return -1;
}
cv::Mat img_gray;
cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY); // In case img is colored
cv::namedWindow("Input Image", cv::WINDOW_AUTOSIZE); // Create a window for display.
cv::imshow("Input Image", img);
cv::Mat hist;
hist = plotHistogram(img_gray);
cv::namedWindow("Histogram", cv::WINDOW_NORMAL); // Create a window for display.
cv::imshow("Histogram", hist);
cv::waitKey(0);
}
cv::Mat plotHistogram(cv::Mat &image, bool cumulative, int histSize) {
// Create Image for Histogram
int hist_w = 1024; int hist_h = 800;
int bin_w = cvRound((double)hist_w / histSize);
cv::Mat histImage(hist_h, hist_w, CV_8UC1, Scalar(255, 255, 255));
if (image.channels() > 1) {
cerr << "plotHistogram: Please insert only gray images." << endl;
return histImage;
}
// Calculate Histogram
float range[] = { 0, 256 };
const float* histRange = { range };
cv::Mat hist;
calcHist(&image, 1, 0, Mat(), hist, 1, &histSize, &histRange);
if (cumulative) {
cv::Mat accumulatedHist = hist.clone();
for (int i = 1; i < histSize; i++) {
accumulatedHist.at<float>(i) += accumulatedHist.at<float>(i - 1);
}
hist = accumulatedHist;
}
// Normalize the result to [ 0, histImage.rows ]
normalize(hist, hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
// Draw bars
for (int i = 1; i < histSize; i++) {
cv::rectangle(histImage, Point(bin_w * (i - 1), hist_h),
Point(bin_w * (i), hist_h - cvRound(hist.at<float>(i))),
Scalar(50, 50, 50), 1);
}
return histImage; // Not really call by value, as cv::Mat only saves a pointer to the image data
}
``
Without looking at the code: the difference between a histogram and a cumulative histogram is that the cumulative histogram at index i has the value of the normal histogram at index i, plus the value of the cumulative histogram at index i - 1. There is a c++ stl algorithm that does the same, and it's called std::partial_sum.
In other words, in image processing a cumulative histogram tells you how many pixels have at most a given color value c
For example, given an array [0, 1, 2, 1, 2, 3, 2, 1, 2, 3, 4] we can plot the histogram and cumulative histogram like so:
The X axis here is the value of the array element while the Y axis is the number of times this element occurs in the array. This is a typical pattern in image processing: in a color histogram the X axis is usually the value of your color. In a 8bpp grayscale image, for example, the X axis has values in the range 0..255. The Y axis then is the number of pixels that have that specific color value.
One important property of a cumulative histogram (in contrast to a normal histogram) is that it's monotonically increasing, i.e. h[i] >= h[i - 1] where i = 1..length(h) and h is the cumulative histogram. This allows operations like histogram equalization. Since a monotonically increasing sequence is by definition also a sorted sequence, you can also perform operation on it that are only allowed on sorted sequences, like binary search.
The next step is usually to calculate a normalized cumulative histogram, which is done by dividing each value in the cumulative histogram by the number of values in your original array. For example
a = [1, 2, 3, 4]
h = hist(a) // histogram of a
ch = partial_sum(h) // cumulative histogram of a
nch = ch / length(a) // normalized, cumulative histogram of a
Another example, given an array [1, 2, 3, 4, 3, 2, 1] we can plot the histogram and cumulative histogram like so:
The X axis here is the 1 based index in the array and the Y axis is the value of that element.
Here is another figure:
And another one, explaining the same:
Starting with what the cumulative histogram is might be good. A histogram is a distribution of the number of pixels according to their intensities but if the thing in question is a cumulative histogram; we don't find counts for a single bin in the vertical axis, but rather we map that counts the cumulative number of pixel intensity values in all of the bins up to the current bin. And linear cumulative distribution or cumulative histogram is essential for some image processing algorithms e.g image equalization.
Histogram (H):
For each pixel of the image
value = Intensity(pixel)
H(value)++
end
The cumulative histogram of the H:
When you set cumulative to true; you are now calculating the cumulative histogram therefore, it is normal for the outputs to be different. In each step of the iteration, you add the previous histogram value to the cumulative histogram.
if (cumulative) {
cv::Mat accumulatedHist = hist.clone();
for (int i = 1; i < histSize; i++) {
accumulatedHist.at<float>(i) += accumulatedHist.at<float>(i - 1);
}
hist = accumulatedHist;
}
You can think of this as a trick when switching from a normal histogram to a cumulative histogram.
accumulatedHist.at<float>(i) += accumulatedHist.at<float>(i - 1);
These references might be useful to understand the basic structure
Histograms
Histogram Equalization
Cumulative Distribution Function
I have been trying to find matched image from sample image using histogram matching. for most of the cases my code is working fine. The range of used method, Bhattacharyya, is 0 <= method <= 1.
normally using Bhattacharyya method the output result will close to 0, in case of matched cases. but i have come to a case where both images are almost similar, though there could be some contrast difference.
which is why this procedure is giving higher result...
can anyone help me why this comparison is giving so much bigger value?
src image and test image
int main(){
src_base = imread("images/src.jpg",-1);
src_test1 = imread("images/test.png",-1);
double base_test1 = hsvToHist(src_base, src_test1,3);
cout<< " Bhattacharyya template Base-Test(1) : "<< base_test1<<endl;
return 0;
}
double hsvToHist( Mat src_base, Mat src_test1, int method){
Mat hsv_base, hsv_test1;
cvtColor( src_base, hsv_base, COLOR_BGR2HSV );
cvtColor( src_test1, hsv_test1, COLOR_BGR2HSV );
/// initialization to calculate histograms (Using 50 bins for hue, 60 for saturation)
int h_bins = 50; int s_bins = 60;
int histSize[] = { h_bins, s_bins };
float h_ranges[] = { 0, 180 };
float s_ranges[] = { 0, 256 };
const float* ranges[] = { h_ranges, s_ranges };
int channels[] = { 0, 1 };
/// Histograms
Mat hist_base, hist_test1;
/// Calculate the histograms for the HSV images
calcHist( &hsv_base, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false );
normalize( hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat() );
calcHist( &hsv_test1, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false );
normalize( hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat() );
///'3' for Bhattacharyya
double base_test1 = compareHist( hist_base, hist_test1, method );
return base_test1;
}
The PNG and JPEG images will have different histograms even though they appear the same, because the JPEG is compressed which means information has been removed and the histogram has been essentially filtered and smoothed. Also, the PNG will have a larger range of values than the JPEG. You may get better results with different bin sizes, but it's hard to tell without testing.
The Bhattacharyya distance has an N^2 term in the denominator where N is the number of pixels. In general, this allows similar values for different sizes of images. However, for the icons that you are comparing, the divisor is much smaller. You could scale the metric by a factor related to the image size.
Alternately, you could use the HISTCMP_CORREL method, which produces lower absolute values if the differences between pixels are less significant. This method produces larger values if more pixels are compared.
When you want similar results independent of differences in image size you could compute both metrics and consider the images equal if one of them passes a tight threshold for similarity. Actual thresholds will vary depending on whether you are comparing color or grayscale images, and whether you have pre-processed the images using histogram equalization (see cv::equalizeHist).
I am calculating a histogram from a greyscale image using the above code; it is working fine.
cv::Mat imgSrc = cv::imread("Image.jpg", cv::IMREAD_UNCHANGED);
cv::Mat histogram; //Array for the histogram
int channels[] = {0};
int binNumArray[] = {256};
float intensityRanges[] = { 0, 256 };
const float* ranges[] = { intensityRanges };
cv::calcHist( &imgSrc, 1, channels, cv::noArray(), histogram, 1, binNumArray, ranges, true, false) ;
In the book of Kaehler & Bradski they refer to this as the "old-fashioned C-style arrays" and they say the new style would use STL vector templates, and the arrays of images from which to calculate the histogram are to be given using cv::InputArrayOfArrays. However, if I try to replace for example the channel array by:
std::vector channels {0};
Gives compilation error.So my questions are these:
1. How can I define the arrays of the 'channels', 'binNumArray', 'intensityRanges' using vectors?
2. How can I define the array of input images using cv::InputArrayOfArrays?
This example show both the "old" approach and the "new" approach, so you can appreciate the difference. It's based on the example found in the OpenCV documentation.
The "new" approach is just a convenience wrapper that internally calls the "old" one.
#include <opencv2\opencv.hpp>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
Mat3b src = imread("path_to_image");
Mat3b hsv;
cvtColor(src, hsv, CV_BGR2HSV);
Mat hist;
Mat hist2;
{
// Quantize the hue to 30 levels
// and the saturation to 32 levels
int hbins = 30, sbins = 32;
int histSize[] = { hbins, sbins };
// hue varies from 0 to 179, see cvtColor
float hranges[] = { 0, 180 };
// saturation varies from 0 (black-gray-white) to
// 255 (pure spectrum color)
float sranges[] = { 0, 256 };
const float* ranges[] = { hranges, sranges };
// we compute the histogram from the 0-th and 1-st channels
int channels[] = { 0, 1 };
calcHist(&hsv, 1, channels, Mat(), // do not use mask
hist, 2, histSize, ranges,
true, // the histogram is uniform
false);
}
{
// Quantize the hue to 30 levels
// and the saturation to 32 levels
vector<int> histSize = { 30, 32 };
// hue varies from 0 to 179, see cvtColor
// saturation varies from 0 (black-gray-white) to
// 255 (pure spectrum color)
vector<float> ranges = { 0, 180, 0, 256 };
// we compute the histogram from the 0-th and 1-st channels
vector<int> channels = { 0, 1 };
vector<Mat> mats = { hsv };
calcHist(mats, channels, Mat(), // do not use mask
hist2, histSize, ranges,
/*true, // the histogram is uniform, this is ALWAYS true*/
false);
}
return 0;
}
I want to find dominant color on an image. For this, I know that I should use image histogram. But I am not sure of image format. Which one of rgb, hsv or gray image, should be used?
After the histogram is calculated, I should find max value on histogram. For this, should I find below maximum binVal value for hsv image? Why my result image contains only black color?
float binVal = hist.at<float>(h, s);
EDIT :
I have tried the below code. I draw h-s histogram. And my result images are here. I don't find anything after binary threshold. Maybe I find max histogram value incorrectly.
cvtColor(src, hsv, CV_BGR2HSV);
// Quantize the hue to 30 levels
// and the saturation to 32 levels
int hbins = 20, sbins = 22;
int histSize[] = {hbins, sbins};
// hue varies from 0 to 179, see cvtColor
float hranges[] = { 0, 180 };
// saturation varies from 0 (black-gray-white) to
// 255 (pure spectrum color)
float sranges[] = { 0, 256 };
const float* ranges[] = { hranges, sranges };
MatND hist;
// we compute the histogram from the 0-th and 1-st channels
int channels[] = {0, 1};
calcHist( &hsv, 1, channels, Mat(), // do not use mask
hist, 2, histSize, ranges,
true, // the histogram is uniform
false );
double maxVal=0;
minMaxLoc(hist, 0, &maxVal, 0, 0);
int scale = 10;
Mat histImg = Mat::zeros(sbins*scale, hbins*10, CV_8UC3);
int maxIntensity = -100;
for( int h = 0; h < hbins; h++ ) {
for( int s = 0; s < sbins; s++ )
{
float binVal = hist.at<float>(h, s);
int intensity = cvRound(binVal*255/maxVal);
rectangle( histImg, Point(h*scale, s*scale),
Point( (h+1)*scale - 1, (s+1)*scale - 1),
Scalar::all(intensity),
CV_FILLED );
if(intensity > maxIntensity)
maxIntensity = intensity;
}
}
std::cout << "max Intensity " << maxVal << std::endl;
Mat dst;
cv::threshold(src, dst, maxIntensity, 255, cv::THRESH_BINARY);
namedWindow( "Dest", 1 );
imshow( "Dest", dst );
namedWindow( "Source", 1 );
imshow( "Source", src );
namedWindow( "H-S Histogram", 1 );
imshow( "H-S Histogram", histImg );
Alternatively you could try a k-means approach. Calculate k clusters with k ~ 2..5 and take the centroid of the biggest group as your dominant color.
The python docu of OpenCv has an illustrated example that gets the dominant color(s) pretty well:
The solution
Find H-S histogram
Find peak H value(using minmaxLoc function)
Split image 3 channel(h,s,v)
Apply to threshold.
Create image by merge 3 channel
Here's a Python approach using K-Means Clustering to determine the dominant colors in an image with sklearn.cluster.KMeans()
Input image
Results
With n_clusters=5, here are the most dominant colors and percentage distribution
[14.69488554 34.23074345 41.48107857] 13.67%
[141.44980073 207.52576948 236.30722987] 15.69%
[ 31.75790423 77.52713644 114.33328324] 18.77%
[ 48.41205713 118.34814452 176.43411287] 25.19%
[ 84.04820266 161.6848298 217.14045211] 26.69%
Visualization of each color cluster
Similarity with n_clusters=10,
[ 55.09073171 113.28271003 74.97528455] 3.25%
[ 85.36889668 145.80759374 174.59846237] 5.24%
[164.17201088 223.34258123 241.81929254] 6.60%
[ 9.97315932 22.79468111 22.01822211] 7.16%
[19.96940211 47.8375841 72.83728002] 9.27%
[ 26.73510467 70.5847759 124.79314278] 10.52%
[118.44741779 190.98204701 230.66728334] 13.55%
[ 51.61750364 130.59930047 198.76335878] 13.82%
[ 41.10232129 104.89923271 160.54431333] 14.53%
[ 81.70930412 161.823664 221.10258949] 16.04%
import cv2, numpy as np
from sklearn.cluster import KMeans
def visualize_colors(cluster, centroids):
# Get the number of different clusters, create histogram, and normalize
labels = np.arange(0, len(np.unique(cluster.labels_)) + 1)
(hist, _) = np.histogram(cluster.labels_, bins = labels)
hist = hist.astype("float")
hist /= hist.sum()
# Create frequency rect and iterate through each cluster's color and percentage
rect = np.zeros((50, 300, 3), dtype=np.uint8)
colors = sorted([(percent, color) for (percent, color) in zip(hist, centroids)])
start = 0
for (percent, color) in colors:
print(color, "{:0.2f}%".format(percent * 100))
end = start + (percent * 300)
cv2.rectangle(rect, (int(start), 0), (int(end), 50), \
color.astype("uint8").tolist(), -1)
start = end
return rect
# Load image and convert to a list of pixels
image = cv2.imread('1.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
reshape = image.reshape((image.shape[0] * image.shape[1], 3))
# Find and display most dominant colors
cluster = KMeans(n_clusters=5).fit(reshape)
visualize = visualize_colors(cluster, cluster.cluster_centers_)
visualize = cv2.cvtColor(visualize, cv2.COLOR_RGB2BGR)
cv2.imshow('visualize', visualize)
cv2.waitKey()
Here are some suggestions to get you started.
All 3 channels in RGB contribute to the color, so you'd have to
somehow figure out where three different histograms are all at maximum. (Or their sum is maximum, or whatever.)
HSV has all of the color (well, Hue) information in one channel, so
you only have to consider one histogram.
Grayscale throws away all color information so is pretty much useless for
finding color.
Try converting to HSV, then calculate the histogram on the H channel.
As you say, you want to find the max value in the histogram. But:
You might want to consider a range of values instead of just one, say
from 20-40 instead of just 30. Try different range sizes.
Remember that Hue is circular, so H=0 and H=360 are the same.
Try plotting the histogram following this:
http://docs.opencv.org/doc/tutorials/imgproc/histograms/histogram_calculation/histogram_calculation.html
to see if your results make sense.
If you're using a range of Hues and you find a range that is maximum, you can either just use the middle of that range as your dominant color, or you can find the mean of the colors within that range and use that.
I calculated H-S Histograms (using opencv) of 100 images for the same object which located in different environment conditions, I need now one average histogram of these 100 histograms!
Thank you in advance
// we compute the histogram from the 0-th and 1-st channels
int channels[] = {0, 1};
calcHist( &hsv, 1, channels, Mat(), // do not use mask
hist, 2, histSize, ranges,
true, // the histogram is uniform
false );
double maxVal=0;
minMaxLoc(hist, 0, &maxVal, 0, 0);
int scale = 10;
Mat histImg = Mat::zeros(sbins*scale, hbins*10, CV_8UC3);
for( int h = 0; h < hbins; h++ )
for( int s = 0; s < sbins; s++ )
{
float binVal = hist.at<float>(h, s);
int intensity = cvRound(binVal*255/maxVal);
rectangle( histImg, Point(h*scale, s*scale),
Point( (h+1)*scale - 1, (s+1)*scale - 1),
Scalar::all(intensity),
CV_FILLED );
}
You could try using addWeighted and go through the array of histograms.
You can set the first weight to 1 and the second one to 1/100.0, and also have your final histogram array use float as the underlaying type.
I did the work by accessing the first bin in hist1, then the first bin in hist2, ....,first bin in hist 100 and find the average of first bins, and so looping for all other 3000 bins
(h_bins*S_bins=3000)
finally I get one average histogram and it works fine for advance proccedures