[OpenCV实战]52 在OpenCV中使用颜色直方图

article/2025/9/28 12:33:42

颜色直方图是一种常见的图像特征,顾名思义颜色直方图就是用来反映图像颜色组成分布的直方图。颜色直方图的横轴表示像素值或像素值范围,纵轴表示该像素值范围内像素点的个数或出现频率。颜色直方图属于计算机视觉中的基础概念,其常常被应用于图像相似度计算,视觉词袋,图像颜色平衡等。颜色直方图可以基于不同的颜色空间和坐标系来实现,本文主要基于RGB颜色空间和直角坐标系计算颜色直方图。

颜色直方图是图像的一种全局颜色特征,优点为方法简单、计算迅速、对旋转和尺度等变化不敏感,缺点是忽略了图像的空间分布信息以及用于相似度对比时往往不那么准确。当然对于颜色直方图有一些改进的变种算法,但是本文只介绍最原始的颜色直方图计算方法。因为改进过的算法提效不高,还不如直接用深度学习。本文主要内容有:颜色直方图的计算、图像均衡化、直方图比较和反向投影,涉及到用于直方图计算的OpenCV函数出自OpenCV_Histograms。

文章目录

  • 1 颜色直方图的计算
  • 2 图像均衡化
  • 3 直方图比较
  • 4 反向投影
  • 5 参考

本文所有代码见:

  • github: OpenCV-Practical-Exercise
  • gitee(备份,主要是下载速度快): OpenCV-Practical-Exercise-gitee

1 颜色直方图的计算

opencv使用内置calcHist函数计算图像的颜色直方图,calcHist函数c++接口如下,python接口类似。

void cv::calcHist(const Mat * images, int nimages, const int * channels,InputArray mask, OutputArray hist, int dims, const int * histSize,const float ** ranges, bool uniform = true, bool accumulate = false);

函数说明如下:

  • images:输入的图像;
  • nimages:输入图像数;
  • channels:用输入图像的第几个颜色通道进行计算;
  • mask:掩模,掩膜的作用为只计算图片中某一区域的直方图,而忽略其他区域;
  • hist:直方图输出结果;
  • dims:输出直方图的维度;
  • histSize:直方图像素值范围分为多少区间(直方图条形个数);
  • ranges:直方图像素值统计范围;
  • uniform=true:是否对得到的直方图数组进行归一化处理;
  • accumulate=false:当输入多个图像时,是否累积计算像素值的个数;

通过calcHist函数能够计算出每个像素范围下的像素点个数,其中histSize表示有多少个像素点区间。比如像素值范围为0到255,如果histSize设置为256,则表示每一个像素值区间跨度为1。如果histSize设置为128,表示每一个像素值区间跨度为256/128=2。以下代码展示了calcHist函数使用方法,分为calcHist计算和结果绘图。结果绘图代码看着很复杂,因为OpenCV绘图功能很一般。可以通过其他的方式绘制图片。

C++

#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace std;
using namespace cv;int main()
{auto imgpath = "image/lena.jpg";// 读取彩色图片Mat src = imread(imgpath, IMREAD_COLOR);if (src.empty()){return -1;}vector<Mat> bgr_planes;// 图像RGB颜色通道分离split(src, bgr_planes);// 将直方图像素值分为多少个区间/直方图有多少根柱子int histSize = 256;// 256不会被使用float range[] = { 0, 256 };const float* histRange = { range };// 一些默认参数,一般不变bool uniform = true, accumulate = false;Mat b_hist, g_hist, r_hist;// 参数依次为:// 输入图像: &bgr_planes[0]// 输入图像个数:1// 使用输入图像的第几个通道:0// 掩膜:Mat()// 直方图计算结果:b_hist,b_hist存储histSize个区间的像素值个数// 直方图维度:1// 直方图像素值范围分为多少区间(直方图条形个数):256// 是否对得到的直方图数组进行归一化处理;uniform// 当输入多个图像时,是否累积计算像素值的个数accumulatecalcHist(&bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate);calcHist(&bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate);calcHist(&bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate);// b_hist表示每个像素范围的像素值个数,其总和等于输入图像长乘宽。// 如果要统计每个像素范围的像素值百分比,计算方式如下// b_hist /= (float)(cv::sum(b_hist)[0]);// g_hist /= (float)(cv::sum(g_hist)[0]);// r_hist /= (float)(cv::sum(r_hist)[0]);/* 以下的参数都是跟直方图展示有关,c++展示图片不那么容易*/// 一些绘图参数int hist_w = 512, hist_h = 400;int bin_w = cvRound((double)hist_w / histSize);// 创建一张黑色背景图像,用于展示直方图绘制结果Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0));// 将直方图归一化到0到histImage.rows,最后两个参数默认就好。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 < histSize; i++){//遍历hist元素(注意hist中是float类型)// 绘制蓝色分量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);}imshow("src image", src);imshow("dst image", histImage);waitKey(0);destroyAllWindows();return 0;
}

Python

import cv2
import numpy as npdef main():imgpath = "image/lena.jpg"src = cv2.imread(imgpath)if src is None:print('Could not open or find the image:', imgpath)return -1bgr_planes = cv2.split(src)histSize = 256# 256会被排除histRange = (0, 256)accumulate = Falseb_hist = cv2.calcHist(bgr_planes, [0], None, [histSize], histRange, accumulate=accumulate)g_hist = cv2.calcHist(bgr_planes, [1], None, [histSize], histRange, accumulate=accumulate)r_hist = cv2.calcHist(bgr_planes, [2], None, [histSize], histRange, accumulate=accumulate)# b_hist表示每个像素范围的像素值个数,其总和等于输入图像长乘宽。# 如果要统计每个像素范围的像素值百分比,计算方式如下assert(sum(b_hist) == src.shape[0] *src.shape[1])# b_hist /= sum(b_hist)# g_hist /= sum(g_hist)# r_hist /= sum(r_hist)# assert(sum(b_hist) == 1)# 以下是绘图代码hist_w = 512hist_h = 400bin_w = int(round(hist_w/histSize))histImage = np.zeros((hist_h, hist_w, 3), dtype=np.uint8)cv2.normalize(b_hist, b_hist, alpha=0, beta=hist_h,norm_type=cv2.NORM_MINMAX)cv2.normalize(g_hist, g_hist, alpha=0, beta=hist_h,norm_type=cv2.NORM_MINMAX)cv2.normalize(r_hist, r_hist, alpha=0, beta=hist_h,norm_type=cv2.NORM_MINMAX)for i in range(1, histSize):cv2.line(histImage, (bin_w*(i-1), hist_h - int(np.round(b_hist[i-1]))),(bin_w*(i), hist_h - int(np.round(b_hist[i]))),(255, 0, 0), thickness=2)cv2.line(histImage, (bin_w*(i-1), hist_h - int(np.round(g_hist[i-1]))),(bin_w*(i), hist_h - int(np.round(g_hist[i]))),(0, 255, 0), thickness=2)cv2.line(histImage, (bin_w*(i-1), hist_h - int(np.round(r_hist[i-1]))),(bin_w*(i), hist_h - int(np.round(r_hist[i]))),(0, 0, 255), thickness=2)cv2.imshow('src image', src)cv2.imshow('dst image', histImage)cv2.waitKey(0)cv2.destroyAllWindows()return 0if __name__ == "__main__":main()

结果如下所示,展示了图片每一个通道的颜色信息。如果是输入是灰度图,稍微修改下代码即可。

类型颜色直方图
输入图片
输出图片

2 图像均衡化

图像均衡化是一种提高图像对比度的方法,通过变换函数将原图像的直方图修正为分布比较均匀的直方图,从而改变图像整体偏暗或整体偏亮,灰度层次不丰富的情况。图像均衡化的具体原理见:直方图均衡化详解。在OpenCV中提供equalizeHist函数实现直方图的均衡化,但是equalizeHist函数只对灰度图进行运算。

以下代码展示了equalizeHist函数的使用。

C++

#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main()
{auto imgpath = "image/lena.jpg";// 读取彩色图片Mat src = imread(imgpath, IMREAD_COLOR);if (src.empty()){return -1;}// 变为灰度图cvtColor(src, src, COLOR_BGR2GRAY);Mat dst;equalizeHist(src, dst);imshow("src image", src);imshow("dst Image", dst);waitKey(0);destroyAllWindows();return 0;
}

Python


import cv2def main():imgpath = "image/lena.jpg"src = cv2.imread(imgpath)if src is None:print('Could not open or find the image:', imgpath)return -1src = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)dst = cv2.equalizeHist(src)cv2.imshow("src image", src)cv2.imshow("dst image", dst)cv2.waitKey(0)cv2.destroyAllWindows()return 0if __name__ == "__main__":main()

结果如下所示,可以看到直方图均衡的作用是扩大颜色直方图像素区间的分布范围,使得分布更加均匀。

类型图片颜色直方图
输入图片
直方图均衡
自适应直方图均衡

但是标准的直方图均衡会导致图中部分区域由于对比度增强过大而成为噪点;或导致一些区域调整后变得更暗/更亮而丢失细节信息。所以面对这种情况,OpenCV提供自适应直方图均衡以获得更好的结果。
自适应直方图均衡的工作原理是将图像划分为MxN个网格,然后将直方图均衡局部应用于每个网格,同时设置对比度限制阈值。结果是输出图像总体上具有更高的对比度(理想情况下)并抑制噪声。OpenCV实现自适应直方图的代码结果如上所示,可以看到直方图分布更加平滑。自适应直方图均衡缺点是效果很依靠手动调整参数(传统图像算法的通病),其具体原理见限制对比度自适应直方图均衡化算法原理、实现及效果,实现代码如下:

C++

#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main()
{auto imgpath = "image/lena.jpg";// 读取彩色图片Mat src = imread(imgpath, IMREAD_COLOR);if (src.empty()){return -1;}// 变为灰度图cvtColor(src, src, COLOR_BGR2GRAY);Mat dst;cv::Ptr<CLAHE> clahe = cv::createCLAHE();// 设置对比度限制阈值clahe->setClipLimit(2);// 设置划分网格数量clahe->setTilesGridSize(cv::Size(16, 16));clahe->apply(src, dst);imshow("src image", src);imshow("dst Image", dst);waitKey(0);destroyAllWindows();return 0;
}

Python


import cv2def main():imgpath = "image/lena.jpg"src = cv2.imread(imgpath)if src is None:print('Could not open or find the image:', imgpath)return -1src = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)clahe = cv2.createCLAHE(clipLimit=2, tileGridSize=(16, 16))dst = clahe.apply(src)cv2.imshow("src image", src)cv2.imshow("dst image", dst)cv2.waitKey(0)cv2.destroyAllWindows()return 0if __name__ == "__main__":main()

如果想对彩色图进行直方图均衡化,则有两种办法:1)分别对RGB三通道均衡化,再组合通道图输出结果;2)将图像颜色空间转化为YUV,YCbCr等颜色空间,仅对亮度通道进行均衡化,最后组合通道图并转回RGB空间。在这里推荐使用第二种办法,具体原因看下面示例代码的结果。所用的转换颜色空间是YUV颜色空间,想要进一步了解YUV颜色空间见YUV图像处理入门1及其他颜色空间见OpenCV中的颜色空间。

#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace std;
using namespace cv;// 颜色通道分别进行均衡化
Mat equalizeHistChannel(const Mat inputImage)
{// 分离通道vector<Mat> channels;split(inputImage, channels);// 各个通道图像进行直方图均衡equalizeHist(channels[0], channels[0]);equalizeHist(channels[1], channels[1]);equalizeHist(channels[2], channels[2]);// 合并结果Mat result;merge(channels, result);return result;
}// 仅对亮度通道进行均衡化
Mat equalizeHistIntensity(const Mat inputImage)
{Mat yuv;// 将bgr格式转换为yuv444cvtColor(inputImage, yuv, COLOR_BGR2YUV);vector<Mat> channels;split(yuv, channels);// 均衡化亮度通道equalizeHist(channels[0], channels[0]);Mat result;merge(channels, yuv);cvtColor(yuv, result, COLOR_YUV2BGR);return result;
}int main()
{auto imgpath = "image/lena.jpg";// 读取彩色图片Mat src = imread(imgpath, IMREAD_COLOR);if (src.empty()){return -1;}Mat dstChannel, dstIntensity;dstChannel = equalizeHistChannel(src);dstIntensity = equalizeHistIntensity(src);imshow("src image", src);imshow("dstChannel image", dstChannel);imshow("dstIntensity image", dstIntensity);waitKey(0);destroyAllWindows();return 0;
}
import cv2# 颜色通道分别进行均衡化
def equalizeHistChannel(inputImage):channels = cv2.split(inputImage)# 各个通道图像进行直方图均衡cv2.equalizeHist(channels[0], channels[0])cv2.equalizeHist(channels[1], channels[1])cv2.equalizeHist(channels[2], channels[2])# 合并结果result = cv2.merge(channels)return result# 仅对亮度通道进行均衡化
def equalizeHistIntensity(inputImage):# 将bgr格式转换为yuv444inputImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2YUV)channels = cv2.split(inputImage)# 均衡化亮度通道cv2.equalizeHist(channels[0], channels[0])# 合并结果result = cv2.merge(channels)result = cv2.cvtColor(result, cv2.COLOR_YUV2BGR)return resultdef main():imgpath = "image/lena.jpg"src = cv2.imread(imgpath)if src is None:print('Could not open or find the image:', imgpath)return -1dstChannel = equalizeHistChannel(src)dstIntensity = equalizeHistIntensity(src)cv2.imshow("src image", src)cv2.imshow("dstChannel image", dstChannel)cv2.imshow("dstIntensity image", dstIntensity)cv2.waitKey(0)cv2.destroyAllWindows()return 0if __name__ == "__main__":main()

结果如下所示,可以看到颜色通道分别均衡化会导致最终合成的图片颜色失真,而仅对亮度通道均衡化则不会。这是因为R、G、B的值是表示亮度,通过对RGB的变化以及它们相互之间的叠加可以得到不同颜色。256级的RGB色彩能够组合约1678(2的24次方)万种色彩,通常简称为千万色或24位色。颜色均衡化是非线性过程,对RGB分别进行均衡化会产生不同的效应,最终导致合成的颜色出现变化。

类型结果
输入图片
颜色通道分别均衡化
仅对亮度通道均衡化

3 直方图比较

我们可以通过比较两幅图片的直方图来衡量两张图片之间的相似程度。OpenCV提供了compareHist函数来实现直方图的比较,也提供了多种直方图度量标准。这些度量标准的取值如下:

enum HistCompMethods {HISTCMP_CORREL        = 0,  // 相关性比较HISTCMP_CHISQR        = 1,  // 卡方比较HISTCMP_INTERSECT     = 2, // 交集比较HISTCMP_BHATTACHARYYA = 3, // 巴氏距离HISTCMP_HELLINGER     = HISTCMP_BHATTACHARYYA, // 等同于巴氏距离HISTCMP_CHISQR_ALT    = 4, // 替代卡方:通常用于纹理比较。HISTCMP_KL_DIV        = 5 //  KL散度
};

以上评价指标可以自行搜索查询相关含义,具体使用哪个评价指标好完全取决于数据集和目标,所以需要通过实验来确定效果最佳的指标。当然也可以自己设计评价指标,不过通过用直方图比较来衡量图片相似性本身效果不太好,各种评价指标都大差不差。直方图比较特点就是快,简单但是不太准。如果想了解其他基于图像处理算法的图片相似度比较方法可以参考基于图像哈希构建图像相似度对比算法。

下面的代码展示了compareHist函数的使用方式,代码综合hsv空间的h通道(色调)和s通道(饱和度)计算图像的颜色直方图。

C++

#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace std;
using namespace cv;int main()
{string imgs[] = { "image/lena.jpg", "image/lena_resize.jpg", "image/lena_flip.jpg","image/test.jpg" };Mat src_base = imread(imgs[0]);Mat src_test1 = imread(imgs[1]);Mat src_test2 = imread(imgs[2]);Mat src_test3 = imread(imgs[3]);if (src_base.empty() || src_test1.empty() || src_test2.empty() || src_test3.empty()){cout << "Could not open or find the images!\n" << endl;return -1;}// 将图片转换到hsv空间Mat hsv_base, hsv_test1, hsv_test2, hsv_test3;cvtColor(src_base, hsv_base, COLOR_BGR2HSV);cvtColor(src_test1, hsv_test1, COLOR_BGR2HSV);cvtColor(src_test2, hsv_test2, COLOR_BGR2HSV);cvtColor(src_test3, hsv_test3, COLOR_BGR2HSV);int h_bins = 50, s_bins = 60;int histSize[] = { h_bins, s_bins };// hue值变化范围为0到179,saturation值变化范围为0到255float h_ranges[] = { 0, 180 };float s_ranges[] = { 0, 256 };const float* ranges[] = { h_ranges, s_ranges };// 使用前两个通道计算直方图int channels[] = { 0, 1 };Mat hist_base, hist_half_down, hist_test1, hist_test2, hist_test3;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());calcHist(&hsv_test2, 1, channels, Mat(), hist_test2, 2, histSize, ranges, true, false);normalize(hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat());calcHist(&hsv_test3, 1, channels, Mat(), hist_test3, 2, histSize, ranges, true, false);normalize(hist_test3, hist_test3, 0, 1, NORM_MINMAX, -1, Mat());// 可以查看枚举变量HistCompMethods中有多少种compare_method方法;for (int compare_method = 0; compare_method < 6; compare_method++){// 不同方法的结果表示含义不一样double base_base = compareHist(hist_base, hist_base, compare_method);double base_test1 = compareHist(hist_base, hist_test1, compare_method);double base_test2 = compareHist(hist_base, hist_test2, compare_method);double base_test3 = compareHist(hist_base, hist_test3, compare_method);printf("method[%d]: base_base : %.3f \t base_test1: %.3f \t base_test2: %.3f \t base_test3: %.3f \n", compare_method, base_base, base_test1, base_test2, base_test3);}printf("Done \n");system("pause");return 0;
}

Python


import cv2def main():imgs = ["image/lena.jpg", "image/lena_resize.jpg", "image/lena_flip.jpg","image/test.jpg"]src_base = cv2.imread(imgs[0])src_test1 = cv2.imread(imgs[1])src_test2 = cv2.imread(imgs[2])src_test3 = cv2.imread(imgs[3])if src_base is None or src_test1 is None or src_test2 is None or src_test3 is None:print('Could not open or find the images!')exit(0)# 将图片转换到hsv空间hsv_base = cv2.cvtColor(src_base, cv2.COLOR_BGR2HSV)hsv_test1 = cv2.cvtColor(src_test1, cv2.COLOR_BGR2HSV)hsv_test2 = cv2.cvtColor(src_test2, cv2.COLOR_BGR2HSV)hsv_test3 = cv2.cvtColor(src_test3, cv2.COLOR_BGR2HSV)h_bins = 50s_bins = 60histSize = [h_bins, s_bins]# hue值变化范围为0到179,saturation值变化范围为0到255h_ranges = [0, 180]s_ranges = [0, 256]# 合并ranges = h_ranges + s_ranges  # 使用前两个通道计算直方图channels = [0, 1]hist_base = cv2.calcHist([hsv_base], channels, None,histSize, ranges, accumulate=False)cv2.normalize(hist_base, hist_base, alpha=0, beta=1, norm_type=cv2.NORM_MINMAX)hist_test1 = cv2.calcHist([hsv_test1], channels, None,histSize, ranges, accumulate=False)cv2.normalize(hist_test1, hist_test1, alpha=0, beta=1, norm_type=cv2.NORM_MINMAX)hist_test2 = cv2.calcHist([hsv_test2], channels, None,histSize, ranges, accumulate=False)cv2.normalize(hist_test2, hist_test2, alpha=0, beta=1, norm_type=cv2.NORM_MINMAX)hist_test3 = cv2.calcHist([hsv_test3], channels, None,histSize, ranges, accumulate=False)cv2.normalize(hist_test3, hist_test3, alpha=0, beta=1, norm_type=cv2.NORM_MINMAX)for compare_method in range(6):base_base = cv2.compareHist(hist_base, hist_base, compare_method)base_test1 = cv2.compareHist(hist_base, hist_test1, compare_method)base_test2 = cv2.compareHist(hist_base, hist_test2, compare_method)base_test3 = cv2.compareHist(hist_base, hist_test3, compare_method)print("method[%s]: base_base : %.3f \t base_test1: %.3f \t base_test2: %.3f \t base_test3: %.3f \n" % (compare_method, base_base, base_test1, base_test2, base_test3))print("Done \n")if __name__ == "__main__":main()

所对比的图片及其在代码中的标识如下所示。其中base图片和test1、test2图片较为相似。test1为base的缩放图,test2是base的水平翻转结果,test3是另外一张完全不同于base的图片。

名字图片标识
lena.jpgbase
lena_resize.jpgtest1
lena_flip.jpgtest2
test.jpgtest3

识别结果如下,各种方法的评价方式不一样。其中base_base表示base图和base图比较的结果,识别结果大体正确。关于base与test1、test2的对比结果,可以看出来颜色直方图对于图像大小、旋转具有尺寸不变性。

method[0]: base_base : 1.000     base_test1: 0.995       base_test2: 0.998       base_test3: -0.005
method[1]: base_base : 0.000     base_test1: 3.911       base_test2: 0.525       base_test3: 40.661
method[2]: base_base : 40.661    base_test1: 33.850      base_test2: 38.536      base_test3: 0.000
method[3]: base_base : 0.000     base_test1: 0.087       base_test2: 0.046       base_test3: 1.000
method[4]: base_base : 0.000     base_test1: 2.814       base_test2: 0.622       base_test3: 83.835
method[5]: base_base : 0.000     base_test1: 8.674       base_test2: 2.128       base_test3: 864.505

4 反向投影

反向投影(Histogram Backprojection)于1990年在论文Indexing via color histograms提出。反向投影的作用简单来说,就是进行图像分割或在图像中查找感兴趣的对象。通过创建了一个与输入图像大小相同(但只有一个通道)的图像,该图片每个像素对应于该像素属于该兴趣对象的概率。一般步骤为计算某一感兴趣区域特征的直方图模型,然后使用这个直方图模型去寻找图像中和该特征相似的区域。在OpenCV中使用calcBackProject函数来实现反向投影。关于calcBackProject函数介绍见calcBackProject 反向投影。

下面示例展示了反向投影的代码,代码以某块草地图片为感兴趣对象,检索输入图像中包含类似草地的区域。代码中涉及到的fliter2D函数使用见cv.filter2D()函数详解。

C++

#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main()
{// 感兴趣区域图片string roipath = "image/test3.jpg";// 目标图片string targetpath = "image/test2.jpg";Mat target = imread(targetpath);Mat roi = imread(roipath);if (target.empty() || roi.empty()){cout << "Could not open or find the images!\n" << endl;return -1;}Mat hsv, hsvt;cvtColor(roi, hsv, COLOR_BGR2HSV);cvtColor(target, hsvt, COLOR_BGR2HSV);// 使用前两个通道计算直方图int channels[] = { 0, 1 };// 计算颜色直方图Mat roihist;int h_bins = 180, s_bins = 256;int histSize[] = { h_bins, s_bins };// hue值变化范围为0到179,saturation值变化范围为0到255float h_ranges[] = { 0, 180 };float s_ranges[] = { 0, 256 };const float* ranges[] = { h_ranges, s_ranges };calcHist(&hsv, 1, channels, Mat(), roihist, 2, histSize, ranges, true, false);// 归一化图片normalize(roihist, roihist, 0, 255, NORM_MINMAX, -1, Mat());// 返回匹配结果图像,dst为一张二值图,白色区域表示匹配到的目标Mat dst;calcBackProject(&hsvt, 1, channels, roihist, dst, ranges, 1);// 应用线性滤波器,理解成去噪就行了Mat disc = getStructuringElement(MORPH_ELLIPSE, Size(7, 7));filter2D(dst, dst, -1, disc);// 阈值过滤Mat thresh;threshold(dst, thresh, 50, 255, 0);// 将thresh转换为3通道图Mat thresh_group[3] = { thresh, thresh, thresh };cv::merge(thresh_group, 3, thresh);imwrite("thresh.jpg", thresh);// 从图片中提取感兴趣区域Mat res;bitwise_and(target, thresh, res);imshow("target", target);imshow("thresh", thresh);imshow("res", res);waitKey(0);destroyAllWindows();return 0;
}

Python


import cv2def main():# 感兴趣区域图片roi = cv2.imread('image/test3.jpg')# 目标图片target = cv2.imread('image/test2.jpg')if roi is None or target is None:print('Could not open or find the images!')return -1hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)hsvt = cv2.cvtColor(target, cv2.COLOR_BGR2HSV)# 计算颜色直方图roihist = cv2.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256])# 归一化图片cv2.normalize(roihist, roihist, 0, 255, cv2.NORM_MINMAX)# 返回匹配结果图像,dst为一张二值图,白色区域表示匹配到的目标dst = cv2.calcBackProject([hsvt], [0, 1], roihist, [0, 180, 0, 256], 1)# 应用线性滤波器,理解成去噪就行了disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))cv2.filter2D(dst, -1, disc, dst)# 阈值过滤ret, thresh = cv2.threshold(dst, 50, 255, 0)# 将thresh转换为3通道图thresh = cv2.merge((thresh, thresh, thresh))# 从图片中提取感兴趣区域res = cv2.bitwise_and(target, thresh)cv2.imshow("target", target)cv2.imshow("thresh", thresh)cv2.imshow("res", res)cv2.waitKey(0)cv2.destroyAllWindows()return 0if __name__ == "__main__":main()

结果如下所示,可以看到反向投影结果是不太准的,毕竟是很简单也是很古老的的算法,了解下就好。真正想要实现图像分割,还是看看深度学习。

类型结果
感兴趣对象
输入对象在这里插入图片描述
反向投影结果
匹配结果

5 参考

  • 视觉词袋
  • OpenCV_Histograms
  • 图像直方图均衡化详解
  • 限制对比度自适应直方图均衡化算法原理、实现及效果
  • YUV图像处理入门1
  • OpenCV中的颜色空间
  • 基于图像哈希构建图像相似度对比算法
  • Indexing via color histograms
  • calcBackProject 反向投影
  • cv.filter2D() 函数详解

http://chatgpt.dhexx.cn/article/FmAyd4c3.shtml

相关文章

关于颜色直方图

一、颜色距 转自&#xff1a;https://blog.csdn.net/jaych/article/details/51137341 1、颜色距离 颜色距离指的是两个颜色之间的差距&#xff0c;通常距离越大&#xff0c;两个颜色相差越大&#xff0c;反之&#xff0c;两个颜色越相近。在计算颜色距离时&#xff0c;有类似…

图像特征分析---颜色特征描述---颜色距和颜色直方图

一、颜色距 转自&#xff1a;https://blog.csdn.net/jaych/article/details/51137341 1、颜色距离颜色距离指的是两个颜色之间的差距&#xff0c;通常距离越大&#xff0c;两个颜色相差越大&#xff0c;反之&#xff0c;两个颜色越相近。在计算颜色距离时&#xff0c;有类似计算…

颜色特征提取(一)——颜色直方图(opencv实现)

直方图——再讲颜色直方图之前&#xff0c;先简单介绍一下直方图。 直方图作为一种简单有效的基于统计特性的特征描述子&#xff0c;在计算机视觉领域广泛使用。它的优点主要体现在两个方面:一是对于任意一个图像区域&#xff0c;直方图特征的提取简单方便;其二&#xff0c;直…

一般颜色直方图

颜色直方图是一种用于图像处理和分析的图表&#xff0c;它可以显示图像中不同颜色的数量。通常&#xff0c;颜色直方图会将颜色分成几个色调区间&#xff0c;每个区间对应一个条形图&#xff0c;其中条形图的高度表示该色调区间中的像素数量。通过颜色直方图&#xff0c;你可以…

网络爬虫Jsoup

简介 网络爬虫&#xff08;又称为网页蜘蛛&#xff0c;网络机器人&#xff0c;在FOAF社区中间&#xff0c;更经常的称为网页追逐者&#xff09;&#xff0c;是一种按照一定的规则&#xff0c;自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、…

【Java】Jsoup爬虫快速入门案例

1、前言 该技术博客是根据B站狂神说Java教程的笔记总结&#xff0c;希望能为大家带来帮助&#xff01; 2、Jsoup实战案例 爬虫可以理解为爬取数据&#xff0c;所谓爬取数据就是&#xff1a; 获取请求返回的页面信息&#xff0c;筛选出我们想要的数据 如果想要爬取数据&#…

android爬虫框架jsoup,Android笔记之JSoup爬虫入门

前言 闲扯一些没用的&#xff0c;写这篇文章之前是有点私心的&#xff0c;因为之前评论某简书大v的文章是鸡汤&#xff0c;瞬间被拉黑&#xff0c;连个解释和说明的机会都没有&#xff0c;文章语言干涩&#xff0c;内容平平&#xff0c;于是就好奇到底是些什么样的人喜欢和吹捧…

Java的Jsoup爬虫

Java的Jsoup爬虫&#xff0c;爬携程酒店评分&#xff0c;保存数据库中 前言一、Jsoup爬虫pom二、逻辑代码部分1.首先我们要先确定爬取的东西&#xff0c;这边我就以携程的酒店评分为例子。2.Jsoup进行解析具体要求爬的内容3.接下来我们就是将爬取的数据存入数据库中 总结 前言 …

使用Jsoup爬虫爬取相关图片

一、Jsoup概述 jsoup 是一款Java 的HTML解析器&#xff0c;可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API&#xff0c;可通过DOM&#xff0c;CSS以及类似于jQuery的操作方法来取出和操作数据。 主要功能&#xff1a; 1. 从一个URL&#xff0c;文件或字符串…

jsoup爬虫实战详解之新浪

** jsoup爬虫实战详解之新浪 ** 今天分享一个之前困扰了一段时间的关于jsoup爬取新浪网页信息的踩坑总结。 在实现以上功能的之前我门首先要了解两个重点&#xff1a;1.关于jsoup的爬取细节以及教程&#xff0c;爬取时所要了解jsoup的相关标签&#xff1a;具体的自己大家感兴…

Java JSOUP爬虫学习分享

昨天从狂神大佬那学习了如何用jsoup爬去网站数据&#xff0c;现在整理了一下给大家分享一下。 先创建一个名叫JsoupPojo的实体类用来装入数据。 Data public class JsoupPojo {private String src;private String name; } 下面是将用来爬取的方法封装成了一个工具类 Compon…

java jsoup爬虫

前言&#xff1a;在日常开发中&#xff0c;我们必定是与我们的数据源打交道&#xff0c;我们的数据源无非就那么几个 1.数据库2.爬虫数据 3.第三方系统交互&#xff0c;这里介绍java 中网页版的爬虫jsoup的使用 1.首先导入我们的jar包 maven坐标如下 org.jsoup jsoup 1.13.1 c…

Jsoup爬虫并解析网页

Jsoup爬虫并解析网页 京东搜索 java&#xff0c;爬取有关java的商品信息 1、获取请求 String url"https://search.jd.com/Search?keywordjava";2、解析网页。&#xff08;Jsoup返回的Document对象就是浏览器的Document对象&#xff09; 所有js中操作Document对象的…

Jsoup爬虫入门实战

一、Jsoup介绍 jsoup 是一款基于 Java 的HTML解析器&#xff0c;它提供了一套非常省力的API&#xff0c;不但能直接解析某个URL地址、HTML文本内容&#xff0c;而且还能通过类似于DOM、CSS或者jQuery的方法来操作数据&#xff0c;所以 jsoup 也可以被当做爬虫工具使用。 相关…

Jsoup爬虫实例

一、简介 jsoup 是一款Java 的HTML解析器&#xff0c;可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API&#xff0c;可通过DOM&#xff0c;CSS以及类似于jQuery的操作方法来取出和操作数据。&#xff08;来源百度&#xff09; 二、准备 首先你需要找到一个你…

使用Jsoup实现网络爬虫

一、什么是Jsoup&#xff1f; jsoup是一款Java的HTML解析器、可以通过URL获取DOM元素并解析&#xff0c;也可对HTML文本内容进行解析&#xff0c;jsoup提供了一套非常简单的方法&#xff0c;类似于CSS、JQuery的操作方法对元素或者是数据进行操作。 二、Jsoup的特点及作用 从…

Java学习笔记:爬虫-用Jsoup解析网页

什么是爬虫 1、爬虫&#xff1a;程序代替人的人工操作&#xff0c;自动获取网页内容&#xff0c;并且从其中提取出来有价值信息。 2、原始&#xff1a;调用Http的类向服务器发出请求&#xff0c;获得HTML&#xff0c;然后用正则表达式等去分析。缺点&#xff1a;难度高。 3、…

jsoup爬虫

文章目录 1、jsoup爬虫简单介绍2、相关代码2.1导入pom依赖2.2、图片爬取2.3、图片本地化 3、百度云链接爬虫 1、jsoup爬虫简单介绍 jsoup 是一款 Java 的HTML 解析器&#xff0c;可通过DOM&#xff0c;CSS选择器以及类似于JQuery的操作方法来提取和操作Html文档数据。 这两个…

解析卷积神经网络学习笔记——魏秀参

第二章 CNN基本部件 1.理解批处理和随机梯度下降&#xff1a; 训练模型时随机选取n个训练样本作为一个batch&#xff08;批输入&#xff09;&#xff0c;那么经过设计好的卷积神经网络就可以输出n个预测值&#xff0c;对这n个预测值求其损失函数&#xff08;注意损失函数绝不是…

【干货】卷积神经网络Alex-Net、VGG-Nets、Network-In-Network案例分析

目录 Alex-Net 网络模型 VGG-Nets 网络模型 Network-In-Network 本文将以 Alex-Net、VGG-Nets、Network-In-Network 为例&#xff0c;分析几类经典的卷积神经网络案例。 在此请读者注意&#xff0c;此处的分析比较并不是不同网络模型精度的“较量”&#xff0c;而是希望读者…