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

news2024/11/25 3:19:37

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


def main():
    imgpath = "image/lena.jpg"
    src = cv2.imread(imgpath)
    if src is None:
        print('Could not open or find the image:', imgpath)
        return -1
    bgr_planes = cv2.split(src)
    histSize = 256
    # 256会被排除
    histRange = (0, 256)
    accumulate = False
    b_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 = 512
    hist_h = 400
    bin_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 0


if __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 cv2

def main():
    imgpath = "image/lena.jpg"
    src = cv2.imread(imgpath)
    if src is None:
        print('Could not open or find the image:', imgpath)
        return -1
    src = 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 0


if __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 cv2

def main():
    imgpath = "image/lena.jpg"
    src = cv2.imread(imgpath)
    if src is None:
        print('Could not open or find the image:', imgpath)
        return -1
    src = 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 0


if __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格式转换为yuv444
	cvtColor(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格式转换为yuv444
    inputImage = 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 result


def main():
    imgpath = "image/lena.jpg"
    src = cv2.imread(imgpath)
    if src is None:
        print('Could not open or find the image:', imgpath)
        return -1
    dstChannel = 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 0


if __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到255
	float 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 cv2

def 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 = 50
    s_bins = 60
    histSize = [h_bins, s_bins]
    # hue值变化范围为0到179,saturation值变化范围为0到255
    h_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到255
	float 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 cv2


def 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 -1
    hsv = 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 0


if __name__ == "__main__":
    main()

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

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

5 参考

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/52491.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ceres库中参数理解

1 参数含义 2 参考链接 Modeling Non-linear Least Squares — Ceres Solver (ceres-solver.org) Ceres详解&#xff08;二&#xff09; CostFunction_他人是一面镜子&#xff0c;保持谦虚的态度的博客-程序员宝宝_ceres costfunction - 程序员宝宝 (cxybb.com)

Linux基础知识与实操-篇七:用户身份切换与特殊控制

文章目录使用者身份的切换配置sudo单一用户群组处理有限制的命令操作特殊shell与PAM模块Linux用户信息传递与当前系统上其他用户对话建立大量账号最后在理解了前篇 篇六:用户权限控制与账号管理 后&#xff0c;我们继续深入用户权限控制关于用户身份切换、限制特殊权限相关的内…

一步步带你用react+spring boot搭建后台之二(登录与首页篇)

前言 最近半年一直在重庆忙于项目上的事情&#xff0c;博客停更了好久&#xff0c;一直想写2个开源项目: 一个是入门级&#xff1a;一步步带你用reactspring boot搭建后台 一个是olap应用系列&#xff1a;一步步构建olap分析平台 今天开始写第一个系列&#xff0c;完整代码随…

LDO(线性稳压器)设计检查

原理图设计规范检查——LDO&#xff08;线性稳压器&#xff09;设计检查 LDO基本概念&#xff1a; LDO即low dropout regulator&#xff0c;是一种低压差线性稳压器&#xff0c;这是相对于传统的线性稳压器来说的。传统的线性稳压器&#xff0c;如78XX系列的芯片都要求输入电…

Linux cifs挂载远程windows共享目录

Linux cifs挂载远程windows共享目录共享windows目录开启共享权限共享磁盘或目录Linux 先决条件安装Linux依赖开启Administrator 用户使用Username/Password挂载临时挂载自动挂载使用Credentials挂载创建CIFS Windows共享凭证文件临时挂载自动挂载终止挂载共享windows目录 开启…

“算力时代”奔涌而来,JASMINER茉莉发布能效更强劲的X16-Q

11月26日&#xff0c;JASMINER茉莉发布X16系列首款静音型算力产品X16-Q&#xff0c;并同步开启全球预售&#xff0c;将为行业带来更高效、更绿色、更智能的智慧算力。 JASMINER X16相较X4系列产品迎来了全新的“进化”&#xff0c;除去新一代JASMINER茉莉自研高通量芯片的应用…

百看不如一练系列 32个python实战项目列表,得不到就毁掉

前言&#xff1a; 不管学习哪门语言都希望能做出实际的东西来&#xff0c;这个实际的东西当然就是项目啦&#xff0c;不用多说大家都知道学编程语言一定要做项目才行。 这里整理了32个Python实战项目列表&#xff0c;都有完整且详细的教程&#xff0c;你可以从中选择自己想做…

Day2多种抓包工具介绍以及使用封包监听工具找到挑战数据包实现发送数据包进行挑战

工具相关证书安装指南 Charles https://blog.csdn.net/weixin_45459427/article/details/108393878 Fidder https://blog.csdn.net/weixin_45043349/article/details/120088449 BurpSuite https://blog.csdn.net/qq_36658099/article/details/81487491 Fiddler&#xff1a; 是一…

PyQt5 不规则窗口的显示

PyQt5 不规则窗口的显示QPixmap和QBitmap绘图的效果对比不可以拖动的不规则窗口可以拖动的不规则窗口不规则窗口实现动画效果加载GIF动画效果函数描述setMask(self, QBitmap)setMask(self, QRegion)setMask()的作用是为调用它的控件增加一个遮罩&#xff0c;遮住所选区域以外的…

【Android App】实战项目之仿微信的附近的人(附源码和演示 超详细)

需要全部源码请点赞关注收藏后评论区留言私信~~~ 艺术家常说“距离产生美”&#xff0c;其实距离近才是优势&#xff0c;谁不希望自己的工作事少钱多离家近呢&#xff1f;不光是工作&#xff0c;像租房买房、恋爱交友&#xff0c;大家都希望找个近点的&#xff0c;比如58、赶集…

【react-笔记】

目录简介基本使用虚拟dom的两种创建方法jsx语法规则模块与组件、模块化和组件化的理解模块组件模块化组件化函数式组件类式组件组件实例三大属性statepropsrefs事件处理包含表单的组件分类非受控组件受控组件高阶函数_函数的柯里化生命周期引出生命周期理解生命周期(旧)总结新的…

Verilog 延迟反标注

延迟反标注&#xff0c; SDF 延迟反标注是设计者根据单元库工艺、门级网表、版图中的电容电阻等信息&#xff0c;借助数字设计工具将延迟信息标注到门级网表中的过程。利用延迟反标注后的网表&#xff0c;就可以进行精确的时序仿真&#xff0c;使仿真更接近实际工作的数字电路…

当MySQL想恋爱,java和navicate抢着做媒婆 ------ java连接MySQL数据库 navicat for MySQL 连接

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 **收录于专栏 数据库 ⭐当MySQL和java通过媒婆navicate谈上了恋爱⭐ 文章目录⭐当MySQL和java通过媒婆navicate谈上了恋爱…

Linux 多线程

目录 一.线程概念 1.什么是线程 2.页表 &#xff08;1&#xff09;页表结构 &#xff08;2&#xff09;好处 3.线程优点 4.线程缺点 5.线程异常 6.线程用途 7.进程和线程的 8.简单使用线程 二.线程控制 1.使用线程 2.线程栈和pthread_t 3.线程的局部存储 4.分离…

[附源码]计算机毕业设计springboot室内设计类网站

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

R语言中的划分聚类模型

划分聚类 是用于基于数据集的相似性将数据集分类为多个组的聚类方法。我们围绕聚类技术进行一些咨询&#xff0c;帮助客户解决独特的业务问题。 【视频】KMEANS均值聚类和层次聚类&#xff1a;R语言分析生活幸福质量系数可视化实例 KMEANS均值聚类和层次聚类&#xff1a;R语言分…

GUI自动化测试工具Sikulix的安装和使用

从程序内部控制对小白来说太难了&#xff0c;所以使用一下自动化测试的工具直接控制按钮达到我的目的 一个比较好的自动化测试工具是Sikulix&#xff0c;这里记录一下安装和基本的使用 下载和安装 官网&#xff1a;http://www.sikulix.com/ 下载对应系统的.jar需要使用java&…

第二证券|A股集体收涨,汽车产业链爆发!房地产延续强势

11月30日早盘&#xff0c;A股三大指数小幅上行。到午间收盘&#xff0c;沪指涨0.21%报3156.50点&#xff0c;深成指涨0.38%&#xff0c;创业板指涨0.20%&#xff1b;两市算计成交5692亿元。 盘面上看&#xff0c;轿车、燃气、地产、油气、煤炭等板块走强&#xff0c;酒店餐饮、…

实验室信息化建设的意义

实验室信息管理系统将实验室的仪器设备、实验人员、实验环境以及相关数据进行集成和管理&#xff0c;使整个实验室形成一个有机整体&#xff0c;规范了业务流程和管理体制&#xff0c;实现各部门之间资源共享协同作业&#xff0c;达到优化配置资源和提高工作效率&#xff0c;不…

欧洲肿瘤生物学博士后申请经历

国外博士后的申请者经常会遇到各种问题&#xff0c;从而感叹申请过程的不易。知识人网小编推荐这篇申请经历&#xff0c;或许会给其他申请者带来一定启示。 作者从今年1月开始申请&#xff0c;5月份获得offer。以下是原文&#xff1a; 背景介绍&#xff1a;国内双非一本生物工…