OpenCV4.8 开发实战系列专栏之 17 - 图像直方图
大家好,欢迎大家学习OpenCV4.8 开发实战专栏,长期更新,不断分享源码。
专栏代码全部基于C++ 与Python双语演示,领学习资料(Free) & 进专栏答疑群,+ VX: OpenCVXueTang_Asst
本文关键知识点:图像直方图
图像直方图是图像像素值的统计学特征、计算代价较小,具有图像平移、旋转、缩放不变性等众多优点,广泛地应用于图像处理的各个领域,特别是灰度图像的阈值分割、基于颜色的图像检索以及图像分类、反向投影跟踪。常见的分为
- 灰度直方图
- 颜色直方图
Bins是指直方图的大小范围, 对于像素值取值在0~255之间的,最少有256个bin,此外还可以有16、32、48、128等,256除以bin的大小应该是整数倍。
OpenCV中相关API
calcHist(&bgr_plane[0], 1, 0, Mat(), b_hist, 1, bins, ranges);
cv.calcHist([image], [i], None, [256], [0, 256])
图像直方图是图像处理中一个非常有用的工具,它能够展示图像中像素值的分布情况。直方图可以基于灰度值或颜色通道来生成,分别对应灰度直方图和颜色直方图。
灰度直方图
灰度直方图展示了图像中灰度级的分布情况。对于灰度图像(通常是8位的,即像素值范围在0到255之间),灰度直方图将展示每个灰度级(0到255)在图像中出现的次数。这有助于我们了解图像的亮度和对比度特性,以及图像中是否存在过曝或欠曝的区域。
颜色直方图
颜色直方图则基于图像的颜色通道来生成。对于彩色图像(如BGR格式的图像),我们可以分别为每个颜色通道(蓝色、绿色、红色)生成直方图,或者生成一个组合的颜色直方图(例如,通过计算每个颜色通道直方图的加权和)。颜色直方图有助于我们了解图像中的颜色分布情况,以及图像中的主要颜色是什么。
Bins(直方图的柱状条)
在直方图中,“bins”指的是用于统计像素值的区间或柱状条。每个bin代表一个灰度级或颜色通道值范围,并且该bin的高度(或面积)表示该范围内像素值的数量。通过调整bins的数量,我们可以控制直方图的粒度:更多的bins意味着更精细的分辨率,但也可能导致直方图变得更加嘈杂;更少的bins则意味着更粗的分辨率,但可能更容易看出整体的分布趋势。
使用OpenCV计算直方图
在OpenCV中,可以使用calcHist
函数来计算图像的直方图。对于灰度图像,我们可能只关心一个通道(即灰度通道),而对于彩色图像,我们可能需要计算多个通道(例如BGR三个通道)的直方图。
灰度图像的直方图
// 假设gray_image是一个灰度图像
int bins = 256; // 灰度级范围从0到255,共256个bins
float range[] = { 0, 256 }; // 像素值范围
const float* histRange = { range };
Mat hist;
calcHist(&gray_image, 1, 0, Mat(), hist, 1, &bins, &histRange);
彩色图像的直方图
// 假设bgr_image是一个BGR格式的彩色图像
int bins = 256; // 每个颜色通道有256个bins
float range[] = { 0, 256 }; // 像素值范围
const float* histRange = { range };
Mat b_hist, g_hist, r_hist;
calcHist(&bgr_image, 1, 0, Mat(), b_hist, 1, &bins, &histRange); // 蓝色通道直方图
calcHist(&bgr_image, 1, 1, Mat(), g_hist, 1, &bins, &histRange); // 绿色通道直方图
calcHist(&bgr_image, 1, 2, Mat(), r_hist, 1, &bins, &histRange); // 红色通道直方图
注意,在上面的代码中,calcHist
函数的第二个参数是通道的数量(对于灰度图像是1,对于彩色图像的单个通道也是1),第三个参数是通道索引(对于灰度图像是0,对于彩色图像,0表示蓝色通道,1表示绿色通道,2表示红色通道)。
对于Python用户,使用OpenCV计算直方图的代码可能如下:
import cv2
import numpy as np
# 读取图像
image = cv2.imread('image.jpg')
# 转换为灰度图像(如果需要)
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 计算灰度直方图
bins = np.arange(257).astype("uint8")
hist_gray = cv2.calcHist([gray_image], [0], None, [256], [0, 256])
# 计算彩色直方图
channels = cv2.split(image)
hist_blue = cv2.calcHist([channels[0]], [0], None, [256], [0, 256])
hist_green = cv2.calcHist([channels[1]], [0], None, [256], [0, 256])
hist_red = cv2.calcHist([channels[2]], [0], None, [256], [0, 256])
请注意,calcHist
函数的参数在Python和C++中略有不同,但基本概念是相似的。
演示代码
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
const int bins = 256;
Mat src;
const char* winTitle = "input image";
void showHistogram();
void drawHistogram(Mat &image);
int main(int argc, char** argv) {
src = imread("D:/images/flower.png", IMREAD_GRAYSCALE);
if (src.empty()) {
printf("could not load image...\n");
return 0;
}
namedWindow(winTitle, WINDOW_AUTOSIZE);
imshow(winTitle, src);
drawHistogram(src);
waitKey(0);
return 0;
}
void drawHistogram(Mat &image) {
// 定义参数变量
const int channels[1] = { 0 };
const int bins[1] = { 256 };
float hranges[2] = { 0,255 };
const float* ranges[1] = { hranges };
int dims = image.channels();
if (dims == 3) {
vector<Mat> bgr_plane;
split(src, bgr_plane);
Mat b_hist;
Mat g_hist;
Mat r_hist;
// 计算Blue, Green, Red通道的直方图
calcHist(&bgr_plane[0], 1, 0, Mat(), b_hist, 1, bins, ranges);
calcHist(&bgr_plane[1], 1, 0, Mat(), g_hist, 1, bins, ranges);
calcHist(&bgr_plane[2], 1, 0, Mat(), r_hist, 1, bins, ranges);
// 显示直方图
int hist_w = 512;
int hist_h = 400;
int bin_w = cvRound((double)hist_w / bins[0]);
Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
// 归一化直方图数据
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
// 绘制直方图曲线
for (int i = 1; i < bins[0]; i++) {
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0), 2, 8, 0);
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(g_hist.at<float>(i))), Scalar(0, 255, 0), 2, 8, 0);
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(r_hist.at<float>(i))), Scalar(0, 0, 255), 2, 8, 0);
}
// 显示直方图
namedWindow("Histogram Demo", WINDOW_AUTOSIZE);
imshow("Histogram Demo", histImage);
}
else {
Mat hist;
// 计算Blue, Green, Red通道的直方图
calcHist(&image, 1, 0, Mat(), hist, 1, bins, ranges);
// 显示直方图
int hist_w = 512;
int hist_h = 400;
int bin_w = cvRound((double)hist_w / bins[0]);
Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
// 归一化直方图数据
normalize(hist, hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
// 绘制直方图曲线
for (int i = 1; i < bins[0]; i++) {
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(hist.at<float>(i))), Scalar(0, 255, 0), 2, 8, 0);
}
// 显示直方图
namedWindow("Histogram Demo", WINDOW_AUTOSIZE);
imshow("Histogram Demo", histImage);
}
}
void showHistogram() {
// 三通道分离
vector<Mat> bgr_plane;
split(src, bgr_plane);
// 定义参数变量
const int channels[1] = { 0 };
const int bins[1] = { 256 };
float hranges[2] = { 0,255 };
const float* ranges[1] = { hranges };
Mat b_hist;
Mat g_hist;
Mat r_hist;
// 计算Blue, Green, Red通道的直方图
calcHist(&bgr_plane[0], 1, 0, Mat(), b_hist, 1, bins, ranges);
calcHist(&bgr_plane[1], 1, 0, Mat(), g_hist, 1, bins, ranges);
calcHist(&bgr_plane[2], 1, 0, Mat(), r_hist, 1, bins, ranges);
// 显示直方图
int hist_w = 512;
int hist_h = 400;
int bin_w = cvRound((double)hist_w / bins[0]);
Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
// 归一化直方图数据
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
// 绘制直方图曲线
for (int i = 1; i < bins[0]; i++) {
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0), 2, 8, 0);
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(g_hist.at<float>(i))), Scalar(0, 255, 0), 2, 8, 0);
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(r_hist.at<float>(i))), Scalar(0, 0, 255), 2, 8, 0);
}
// 显示直方图
namedWindow("Histogram Demo", WINDOW_AUTOSIZE);
imshow("Histogram Demo", histImage);
}
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
const int bins = 256;
Mat src;
const char* winTitle = "input image";
void showHistogram();
int main(int argc, char** argv) {
src = imread("D:/vcprojects/images/flower.png");
if (src.empty()) {
printf("could not load image...\n");
return 0;
}
namedWindow(winTitle, WINDOW_AUTOSIZE);
imshow(winTitle, src);
showHistogram();
waitKey(0);
return 0;
}
void showHistogram() {
// 三通道分离
vector<Mat> bgr_plane;
split(src, bgr_plane);
// 定义参数变量
const int channels[1] = { 0 };
const int bins[1] = { 256 };
float hranges[2] = { 0,255 };
const float* ranges[1] = { hranges };
Mat b_hist;
Mat g_hist;
Mat r_hist;
// 计算Blue, Green, Red通道的直方图
calcHist(&bgr_plane[0], 1, 0, Mat(), b_hist, 1, bins, ranges);
calcHist(&bgr_plane[1], 1, 0, Mat(), g_hist, 1, bins, ranges);
calcHist(&bgr_plane[2], 1, 0, Mat(), r_hist, 1, bins, ranges);
// 显示直方图
int hist_w = 512;
int hist_h = 400;
int bin_w = cvRound((double)hist_w / bins[0]);
Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
// 归一化直方图数据
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
// 绘制直方图曲线
for (int i = 1; i < bins[0]; i++) {
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0), 2, 8, 0);
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(g_hist.at<float>(i))), Scalar(0, 255, 0), 2, 8, 0);
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(r_hist.at<float>(i))), Scalar(0, 0, 255), 2, 8, 0);
}
// 显示直方图
namedWindow("Histogram Demo", WINDOW_AUTOSIZE);
imshow("Histogram Demo", histImage);
}
python 代码演示
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
def custom_hist(gray):
h, w = gray.shape
hist = np.zeros([256], dtype=np.int32)
for row in range(h):
for col in range(w):
pv = gray[row, col]
hist[pv] += 1
y_pos = np.arange(0, 256, 1, dtype=np.int32)
plt.bar(y_pos, hist, align='center', color='r', alpha=0.5)
plt.xticks(y_pos, y_pos)
plt.ylabel('Frequency')
plt.title('Histogram')
# plt.plot(hist, color='r')
# plt.xlim([0, 256])
plt.show()
def image_hist(image):
cv.imshow("input", image)
color = ('blue', 'green', 'red')
for i, color in enumerate(color):
hist = cv.calcHist([image], [i], None, [256], [0, 256])
plt.plot(hist, color=color)
plt.xlim([0, 256])
plt.show()
src = cv.imread("D:/vcprojects/images/flower.png")
cv.namedWindow("input", cv.WINDOW_AUTOSIZE)
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
cv.imshow("input", gray)
# custom_hist(gray)
image_hist(src)
cv.waitKey(0)
cv.destroyAllWindows()
结束语
学习贵在坚持,学习OpenCV贵在每一天的代码练习,原理跟基本的函数解释,相关知识,后续更新边学边理解,搞技术永远要坚持做长期主义者!我们一起努力!!!
注:领学习资料(Free) & 进专栏答疑群,+ VX: OpenCVXueTang_Asst