当前位置: 首页 > article >正文

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


http://www.kler.cn/a/418129.html

相关文章:

  • ROS2-参数服务器
  • 第六届金盾信安杯-SSRF
  • Mybatis 支持延迟加载的详细内容
  • 数据结构-最小生成树
  • uniapp运行时,同步资源失败,未得到同步资源的授权,请停止运行后重新运行,并注意手机上的授权提示。
  • 【AI技术赋能有限元分析应用实践】Abaqus有限元分析到深度学习方法应用全过程——汽车刹车片热力耦合分析
  • (SAST 检测规-5)不良授权和身份验证
  • 《C++ Primer Plus》学习笔记|第9章 内存模型和名称空间 (24-12-1更新)
  • 深入理解 Docker 在 CI/CD 流程中的应用原理
  • 处理HTTP请求的两种常见方式:多个处理器(Handler)、多个处理函数(HandleFunc),两者有什么区别
  • 传智杯 A字符串拼接
  • vxe-table 树形表格的详细用法、树形表格懒加载
  • 从实战出发,精通Cache设计与性能优化
  • 【iOS】OC高级编程 iOS多线程与内存管理阅读笔记——自动引用计数(二)
  • 机器学习模型从理论到实战|【007-SVM 支持向量机】 SVM的情感分类
  • js常见函数实现
  • Ubuntu 操作系统
  • 基于51单片机的心率体温监测报警系统(包括程序、仿真、原理图、流程图)
  • 2024年9月 GESP C++等级考试 真题及解析 三级
  • 理解Java集合的基本用法—Collection:List、Set 和 Queue,Map
  • day03(Linux底层)Tftp服务器环境搭建
  • 43 基于单片机的温度和烟雾检测
  • 计算机的错误计算(一百七十一)
  • jQuery零基础入门速通(下)
  • 新版本PasteSpider开发中专用部署工具介绍(让2GB的服务器也能使用CI/CD,简化你的部署过程)
  • 【ROS 机器人快速入门】