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

news/2024/10/19 9:39:24/

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

相关文章

11月动态|通过PWmat计算的离子浓度自由能相关文献发表在JCTC

11月 11月&#xff0c;龙讯旷腾完成Q-Flow和Q-Studio新版本的升级&#xff0c;完成了40余项功能的更新和上线&#xff1b;签约并行科技在高性能计算领域再下一城&#xff1b;汪林望博士受海河实验室邀请作线上主题报告&#xff1b;通过PWmat计算的离子浓度自由能相关文献发表在…

RK3588平台开发系列讲解(PWM篇)PWM及backlight的使用方法

平台内核版本安卓版本RK3588Linux 5.10Android12🚀返回专栏总目录 文章目录 一、PWM驱动二、DTS配置三、PWM在user space的使用四、PWM在背光中的使用4.1 Backlight DTS4.2 PWM Backlight 调试沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍PWM以及backli…

SpringBoot+MyBatis和MyBatisPlus+vue+ElementUl实现批量删除 我只能说太简单了

目录准备工作MySQL数据库表Result返回结果1、SpringBootMyBatisPlusvueElementUl实现批量删除1.1、演示GIF动态图1.2、实体类1.3、Dao接口1.4、Service接口1.5、ServiceImpl接口实现层1.6、Controller控制层1.7、Vue前端2、SpringBootMyBatisvueElementUl实现批量删除2.1、演示…

简单封装一个易拓展的Dialog

Dialog&#xff0c;每个项目中多多少少都会用到&#xff0c;肯定也会有自己的一套封装逻辑&#xff0c;无论如何封装&#xff0c;都是奔着简单复用的思想&#xff0c;有的是深层次的封装&#xff0c;也就是把相关的UI效果直接封装好&#xff0c;暴露可以修改的属性和方法&#…

【吴恩达机器学习笔记】八、应用机器学习的建议

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4e3;专栏定位&#xff1a;为学习吴恩达机器学习视频的同学提供的随堂笔记。 &#x1f4da;专栏简介&#xff1a;在这个专栏&#xff0c;我将整理吴恩达机器学习视频的所有内容的笔记&…

DataFrame API入门操作及代码展示

文章目录DataFrame风格编程DSL风格编程代码示例相关API相关代码示例SQL风格编程代码示例相关API相关代码Fucntions包基于SparkSQL的WordCount代码编写DataFrame风格编程 DataFrame支持两种风格进行编程 DSL风格SQL风格 DSL称之为领域特定语言&#xff0c;其实就是指DataFrame特…

会员营销中,数字会员模式如何打造差异化会员服务

在现在的市场上&#xff0c;针对用户运营这个环节&#xff0c;一般企业采用最多的就是进行会员营销&#xff0c;这是为什么呢&#xff1f;这是因为对于会员&#xff0c;用户是没有过多的抵触的&#xff0c;在以往用户的惯有概念中&#xff0c;会员是普遍存在的&#xff0c;拥有…

TensorFlow之文本分类算法-5

1 前言 2 收集数据 3 探索数据 4 选择模型 5 准备数据 6 模型-构建训练评估 构建输出层 构建n-gram模型 根据前面章节的描述&#xff0c;n-gram模型是独立地处理分词&#xff0c;与原文中的单词顺序不相关。简单的多层神经感知&#xff08;逻辑回归&#xff09;、梯度推…