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.
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 am trying to detect blur rate of the face images with below code.
cv::Mat greyMat;
cv::Mat laplacianImage;
cv::Mat imageClone = LapMat.clone();
cv::resize(imageClone, imageClone, cv::Size(150, 150), 0, 0, cv::INTER_CUBIC);
cv::cvtColor(imageClone, greyMat, CV_BGR2GRAY);
Laplacian(greyMat, laplacianImage, CV_64F);
cv::Scalar mean, stddev; // 0:1st channel, 1:2nd channel and 2:3rd channel
meanStdDev(laplacianImage, mean, stddev, cv::Mat());
double variance = stddev.val[0] * stddev.val[0];
cv::Mat M = (cv::Mat_(3, 1) << -1, 2, -1);
cv::Mat G = cv::getGaussianKernel(3, -1, CV_64F);
cv::Mat Lx;
cv::sepFilter2D(LapMat, Lx, CV_64F, M, G);
cv::Mat Ly;
cv::sepFilter2D(LapMat, Ly, CV_64F, G, M);
cv::Mat FM = cv::abs(Lx) + cv::abs(Ly);
double focusMeasure = cv::mean(FM).val[0];
return focusMeasure;
it some times gives not good results as attached picture.
Is there a best practice way to detect blurry faces ?
I attached an example image which is high scored with above code which is false.
Best
I'm not sure how are you interpreting your results. To measure blur, you usually take the output of the Blur Detector (a number) and compare it against a threshold value, then determine if the input is, in fact, blurry or not. I don't see such a comparison in your code.
There are several ways to measure "blurriness", or rather, sharpness. Let's take a look at one. It involves computing the variance of the Laplacian and then comparing it to an expected value. This is the code:
//read the image and convert it to grayscale:
cv::Mat inputImage = cv::imread( "dog.png" );
cv::Mat gray;
cv::cvtColor( inputImage, gray, cv::COLOR_RGB2GRAY );
//Cool, let's compute the laplacian of the gray image:
cv::Mat laplacianImage;
cv::Laplacian( gray, laplacianImage, CV_64F );
//Prepare to compute the mean and standard deviation of the laplacian:
cv::Scalar mean, stddev;
cv::meanStdDev( laplacianImage, mean, stddev, cv::Mat() );
//Let’s compute the variance:
double variance = stddev.val[0] * stddev.val[0];
Up until this point, we've effectively calculated the variance of the Laplacian, but we still need to compare against a threshold:
double blurThreshold = 300;
if ( variance <= blurThreshold ) {
std::cout<<"Input image is blurry!"<<std::endl;
} else {
std::cout<<"Input image is sharp"<<std::endl;
}
Let’s check out the results. These are my test images. I've printed the variance value in the lower-left corner of the images. The threshold value is 300, blue text is within limits, red text is below.
I need to find the most present color in an image with OpenCV. I referred https://docs.opencv.org/2.4/modules/imgproc/doc/histograms.html?highlight=calchist and when I run that code I get for the H-S histogram is as below image. How do I tell the most present color from that histogram? Can someone please tell me how I get the most present color in an image using the histogram for HSV? (I am using C++)
As far as I can see (a very ambigous description on opencv site) here we have Hue on first axis, Saturation on second axis and color intensity as point brightness.
Hue varies from 0 (red) to 180 (from violet to red). Saturaion varies from 0 to 255 (black-gray-white). Hue is quantized from 180 to 30. Saturation is quantized from 255 to 32 (in accordance with your link to opencv site). The brighter square area on histogram, the brighter some combination of hue and saturation on your image.
I reproduced OpenCV sample on my PC. I added my source image and HS histogram. On HS histogram we can see one bright rectangle corresponding to blue color of medium saturation
I also added source modified for OpenCV 3.4.0
#include <Windows.h>
#include <Vfw.h>
#include "opencv2\core\core.hpp"
#include "opencv2\imgproc\imgproc.hpp"
#include "opencv2\imgcodecs\imgcodecs.hpp"
#include "opencv2\highgui\highgui.hpp"
using namespace cv;
int _tmain(int argc, _TCHAR* argv[])
{
Mat src, hsv;
src=imread("blue_circle.jpg");
cvtColor(src, hsv, CV_BGR2HSV);
// 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 };
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);
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 );
}
namedWindow( "Source", CV_WINDOW_FREERATIO );
imshow( "Source", src );
namedWindow( "H-S Histogram", CV_WINDOW_FREERATIO );
imshow( "H-S Histogram", histImg );
waitKey(0);
return 0;
}
Judging from the image you've posted, and a very brief read of the description at the link, it looks like the rows represent hues and columns represent saturations. So it looks like the above image is saying that a hue of 0 and a saturation of about 3/4ths of the max is the color with the most occurrences. Usually a hue of 0 is red, so it's probably a fairly bright, fairly pure red in this case.
Maybe you need a colormap like this:
(The image is taken from my another answer : Choosing the correct upper and lower HSV boundaries for color detection with`cv::inRange` (OpenCV))
Notice that the Hue domains the color so I scale it 4X. The x axis is H, the y axis is S, while keep V=255.
Then according the maxval position (H-S), lookup the colormap to get the color.
Another example is that:
For this image:
Calculate the H-S hist, log it, threshold to get the domain colors' mask, like this:
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 have the following picture
And I try to count all candies, but some of them intersect. And my following code doesn't work. The Code below can only identify candies if they don't intersect. But I can't have any thoughts about what to do if they intersect
int main( int argc, char** argv )
{
Mat src = imread("C:\\data\\Assignment1A.jpg",0); // reads image from file
Mat dst = Mat::zeros(src.rows, src.cols, CV_8UC3);
cv::Scalar min(245, 0, 0);
cv::Scalar max(255, 255, 255);
cv::inRange( src, min, max, dst);
namedWindow( "Source", 1 );
imshow( "Source", dst );
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours( dst, contours, hierarchy,
CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );
cout <<contours.size();
size_t count = contours.size();
int i = 0;
for(size_t i = 0; i < contours.size(); i++) {
cv::Point2f c;
float r;
cv::minEnclosingCircle( contours[i], c, r);
if (r > 10) {
i++; }
}
cout << i << " candies";
waitKey(0);
return 0;
}
Can anyone help me how to solve this problem?
This is probably not a very good solution (it only works under assumption that candies of the same color never overlap). (There's also this article about using distance transform and watershed, but I couldn't make it fork for this, maybe the candies overlap too much?)
Remove the white background (especially because it has a lot of noise and JPEG artifacts).
Convert to HSV and take the H channel (as suggested in the comments). Hue is useful here, because it sort of represents a color with a single value (hue) instead of three (red, green, blue), ignoring, to a degree, all those surface irregularities. You probably can't distinguish some of the ellipses below from the background, but don't worry, your computer can.
For a narrow range of H (say, [1, 20]), find contours big enough (in terms of their area) to be a single candy, but small enough not to be more than one candy. Repeat for H in range [21, 40], then [41, 60], and so on, until you reach the top hue value of 179. The contours are not too good, but good enough for counting. You can probably improve them by doing dilate/erode at every step to get rid of the junk, or something, if you need to.
The Python code to show how it works:
import cv2
import numpy as np
#get only contours of more or less the single candy size
#noise is not included because its area is too small
#overlapping candies are not included because their area is too big
#probably there are smarter ways (area/circumference ratio, ellipse fitting, etc.)
def get_contours(img):
area_threshold_max = 120*120 #nice hard-coded empirical values
area_threshold_min = 50*50
_, contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
areas = [cv2.contourArea(c) for c in contours]
contours = [c for n, c in enumerate(contours) if area_threshold_min < areas[n] < area_threshold_max]
return contours
#remove background
img_g = cv2.imread("candy.jpg", 0) #read as grayscale
_, thresh = cv2.threshold(img_g, 235, 1, cv2.THRESH_BINARY_INV) #mask to include everything that isn't too white
img = cv2.imread("candy.jpg") #read again as a color image
black = np.zeros(img.shape).astype(img.dtype) #black image to combine with
img = cv2.bitwise_or(img, black, mask = thresh)
#convert to HSV and take only the H value
img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)[:, :, 0]
#find contours of a more or less appropriate size within a narrow H range,
#remove them from the image, increment H, repeat
contours_all = []
h_step = 20
for h_start in range(0, 180, h_step):
rng = np.zeros(img.shape).astype(np.uint8)
rng = cv2.inRange(img, h_start + 1, h_start + h_step)
contours = get_contours(rng)
contours_all += contours
#draw our contours with their numbers on top of the original image
img_to_draw = cv2.imread("candy.jpg")
cv2.drawContours(img_to_draw, contours_all, -1, (0, 0, 0), 2)
for i, cnt in enumerate(contours_all):
(x, y), radius = cv2.minEnclosingCircle(cnt)
cv2.putText(img_to_draw, "%d" % (i + 1), (int(x-20), int(y+10)), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 3)
cv2.putText(img_to_draw, "%d candies" % len(contours_all), (5, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 3)
cv2.imwrite("result2.png", img_to_draw)