OpenCV如何实现背投(58)

 返回:OpenCV系列文章目录(持续更新中......)

上一篇:OpenCV直方图比较(57)
下一篇:OpenCV如何模板匹配(59)

目标

在本教程中,您将学习:

  • 什么是背投以及它为什么有用
  • 如何使用 OpenCV 函数 cv::calcBackProject 计算背投
  • 如何使用 OpenCV 函数 cv::mixChannels 混合图像的不同通道

cv::mixChannels 和 cv::calcBackProject 都是 OpenCV 库中常用的图像处理函数,主要应用于通道处理、直方图反向投影等操作。

cv::mixChannels 是一个通道处理函数,它可以在不同的图像通道之间进行拷贝、转换和简单操作等处理。该函数的主要思路是,定义一个通道映射表来指定源和目标图像之间的通道拷贝关系,并在映射表中指定每个通道的源图像和目标图像之间的通道位置和通道数。通过 mixChannels 函数,我们可以实现在不同通道间进行像素值的传递和处理等图像操作。

cv::calcBackProject 是一个直方图反向投影函数,它可以用于将某个模型区域的像素在目标图像中得到的直方图投影回去,以便进行目标检测和跟踪等操作。该函数主要思路是,首先使用 calcHist 函数计算模型区域的直方图,然后在目标图像中使用 calcBackProject 函数计算每个像素点在该直方图上的投影分布,并生成反向投影图像。利用反向投影图像,我们可以定位目标区域,并识别出图像中存在的目标区域。

因此,cv::mixChannels 和 cv::calcBackProject 函数通常会一起使用。通过 mixChannels 函数进行通道拷贝和转换处理,然后利用 calcBackProject 函数生成反向投影图像,可以实现更加精确的目标区域定位和识别。这些函数广泛用于计算机视觉和图像处理等领域中的特定应用和算法中。

理论

什么是背投?

  • 背投是一种记录给定图像的像素与直方图模型中像素分布的拟合程度的方法。
  • 为简化起见:对于“背投”,您可以计算要素的直方图模型,然后使用它在影像中查找此要素。
  • 应用示例:如果您有肉色直方图(例如,色相饱和度直方图),则可以使用它来查找图像中的肉色区域:

它是如何工作的?

  • 我们通过使用皮肤示例来解释这一点:
  • 假设您已经根据下图获得了皮肤直方图(色相饱和度)。此外,直方图将是我们的模型直方图(我们知道它代表了肤色的样本)。您应用了一些蒙版来仅捕获皮肤区域的直方图
  •  

  • 现在,让我们想象一下,你得到另一个手部图像(测试图像),如下所示:(及其各自的直方图):

     

  • 我们想要做的是使用我们的模型直方图(我们知道它代表皮肤色调)来检测测试图像中的皮肤区域。步骤如下
    1. 在我们的测试图像的每个像素即p(i,j)中,收集数据并找到该像素的相应箱位置即 h{i,j}, s{i,j} )。
    2. 在相应的 bin 中查找模型直方图  h{i,j}, s{i,j}  - 并读取 bin 值。
    3. 将此图柱值存储在新图像 (BackProjection) 中。此外,您可以考虑先对模型直方图进行归一化,以便您可以看到测试图像的输出。
    4. 应用上述步骤,我们得到以下测试图像的 BackProjection 图像:

  1. 在统计方面,存储在 BackProjection 中的值表示测试图像中的像素属于皮肤区域的概率,基于我们使用的模型直方图。例如,在我们的测试图像中,较亮的区域更有可能是皮肤区域(实际上确实如此),而较暗区域的可能性较小(请注意,这些“黑暗”区域属于带有一些阴影的表面,这反过来又会影响检测)。

C++代码
 

  • 这个程序是做什么的?
    • 加载图像
    • 将原始格式转换为 HSV 格式,并仅分离用于直方图的 Hue 通道(使用 OpenCV 函数 cv::mixChannels )
    • 让用户输入用于计算直方图的箱数。
    • 计算直方图(并在条柱更改时更新它)和同一图像的背投。
    • 在窗口中显示背投和直方图。

  • 可下载代码
    • 单击此处获取基本版本(在本教程中解释)。
    • 对于稍微花哨的东西(使用 H-S 直方图和 floodFill 为皮肤区域定义蒙版),您可以查看改进的演示
    • ...或者,您可以随时查看示例中的经典 CamshiftDemo。
  • 代码一览:
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
 
#include <iostream>
 
using namespace cv;
using namespace std;
 
Mat hue;
int bins = 25;
 
void Hist_and_Backproj(int, void* );
 
int main( int argc, char* argv[] )
{
 CommandLineParser parser( argc, argv, "{@input |Back_Projection_Theory0.jpg| input image}" );
 samples::addSamplesDataSearchSubDirectory("doc/tutorials/imgproc/histograms/back_projection/images");
 Mat src = imread(samples::findFile(parser.get<String>( "@input" )) );
 if( src.empty() )
 {
 cout << "Could not open or find the image!\n" << endl;
 cout << "Usage: " << argv[0] << " <Input image>" << endl;
 return -1;
 }
 
 Mat hsv;
 cvtColor( src, hsv, COLOR_BGR2HSV );
 
 hue.create(hsv.size(), hsv.depth());
 int ch[] = { 0, 0 };
 mixChannels( &hsv, 1, &hue, 1, ch, 1 );
 
 const char* window_image = "Source image";
 namedWindow( window_image );
 createTrackbar("* Hue bins: ", window_image, &bins, 180, Hist_and_Backproj );
 Hist_and_Backproj(0, 0);
 
 imshow( window_image, src );
 // Wait until user exits the program
 waitKey();
 
 return 0;
}
 
void Hist_and_Backproj(int, void* )
{
 int histSize = MAX( bins, 2 );
 float hue_range[] = { 0, 180 };
 const float* ranges[] = { hue_range };
 
 Mat hist;
 calcHist( &hue, 1, 0, Mat(), hist, 1, &histSize, ranges, true, false );
 normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );
 
 Mat backproj;
 calcBackProject( &hue, 1, 0, hist, backproj, ranges, 1, true );
 
 imshow( "BackProj", backproj );
 
 int w = 400, h = 400;
 int bin_w = cvRound( (double) w / histSize );
 Mat histImg = Mat::zeros( h, w, CV_8UC3 );
 
 for (int i = 0; i < bins; i++)
 {
 rectangle( histImg, Point( i*bin_w, h ), Point( (i+1)*bin_w, h - cvRound( hist.at<float>(i)*h/255.0 ) ),
 Scalar( 0, 0, 255 ), FILLED );
 }
 
 imshow( "Histogram", histImg );
}

解释

读取输入图像:

CommandLineParser parser( argc, argv, "{@input |Back_Projection_Theory0.jpg| input image}" );
 samples::addSamplesDataSearchSubDirectory("doc/tutorials/imgproc/histograms/back_projection/images");
 Mat src = imread(samples::findFile(parser.get<String>( "@input" )) );
 if( src.empty() )
 {
 cout << "Could not open or find the image!\n" << endl;
 cout << "Usage: " << argv[0] << " <Input image>" << endl;
 return -1;
 }

将其转换为 HSV 格式:

 Mat hsv;
 cvtColor( src, hsv, COLOR_BGR2HSV );

在本教程中,我们将仅将 Hue 值用于我们的一维直方图(如果您想使用更标准的 H-S 直方图,请查看上面链接中的更高级代码,这会产生更好的结果):

 hue.create(hsv.size(), hsv.depth());
 int ch[] = { 0, 0 };
 mixChannels( &hsv, 1, &hue, 1, ch, 1 );
  • 如您所见,我们使用函数 cv::mixChannels 仅从 hsv 图像中获取通道 0(色相)。它获取以下参数:
    • &HSV:将从中复制通道的源数组
    • 1:源数组的数量
    • 色相(&C):复制通道的目标数组
    • 1:目标数组的数量
    • ch[] = {0,0}:指示如何复制通道的索引对数组。在本例中,将 &hsv 的 Hue(0) 通道复制到 &hue 的 0 通道(1 通道)
    • 1:索引对数
  • 为用户创建用于输入图格值的跟踪栏。对 Trackbar 的任何更改都意味着对 Hist_and_Backproj回调函数的调用。

 const char* window_image = "Source image";
 namedWindow( window_image );
 createTrackbar("* Hue bins: ", window_image, &bins, 180, Hist_and_Backproj );
 Hist_and_Backproj(0, 0);

显示图像并等待用户退出程序:

 imshow( window_image, src );
 // Wait until user exits the program
 waitKey();

Hist_and_Backproj功能:初始化 cv::calcHist 所需的参数。条柱数量来自 Trackbar:

 int histSize = MAX( bins, 2 );
 float hue_range[] = { 0, 180 };
 const float* ranges[] = { hue_range };

计算直方图并将其归一化为范围 [0,255]

 Mat hist;
 calcHist( &hue, 1, 0, Mat(), hist, 1, &histSize, ranges, true, false );
 normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );

 通过调用函数 cv::calcBackProject 获取同一图像的反向投影

 Mat backproj;
 calcBackProject( &hue, 1, 0, hist, backproj, ranges, 1, true );
  • 所有参数都是已知的(与用于计算直方图的参数相同),只是我们添加了 BackProj 矩阵,它将存储源图像 (&hue) 的反向投影
  • 显示 backproj:

 imshow( "BackProj", backproj );

绘制图像的一维色相直方图:

 int w = 400, h = 400;
 int bin_w = cvRound( (double) w / histSize );
 Mat histImg = Mat::zeros( h, w, CV_8UC3 );
 
 for (int i = 0; i < bins; i++)
 {
 rectangle( histImg, Point( i*bin_w, h ), Point( (i+1)*bin_w, h - cvRound( hist.at<float>(i)*h/255.0 ) ),
 Scalar( 0, 0, 255 ), FILLED );
 }
 
 imshow( "Histogram", histImg );

结果

以下是使用示例图像的输出(你猜怎么着?另一只手)。您可以使用 bin 值,您将观察它如何影响结果:

参考文献:

1、《Back Projection》-----Ana Huamán

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

轻松下载小程序短剧视频,你不可错过的神器!

探索小程序的精彩短剧&#xff0c;一款神器让您的收藏变得触手可及。轻松下载&#xff0c;无损享受&#xff0c;每个精彩瞬间&#xff0c;都不让你错过。让这款下载神器成为你掌中的宝藏&#xff0c;开启随时随地的精彩观影体验。 这个神器就是下载高手 下载小程序视频的工具…

GoLang Gin实际使用

所有代码同步到Admin/gitDemo - Gitee.comhttps://gitee.com/mec-deployment-team_0/git-demo/tree/dev/ 1.创建Gin框架 一般设计一个常规的web项目&#xff0c;都需要以下几个模块 runApp 主函数&#xff0c;运行整个项目routes 路由控制&#xff0c;管理跳转以及路由分组co…

CTF-Show nodejs

web334 下载附件&#xff0c;有两个文件 在Character.toUpperCase()函数中&#xff0c;字符ı会转变为I&#xff0c;字符ſ会变为S。 在Character.toLowerCase()函数中&#xff0c;字符İ会转变为i&#xff0c;字符K会转变为k。 所以用ctfſhow 123456登录就可以出flag了 w…

力扣刷题第一天:消失的数字

大家好啊&#xff0c;从今天开始将会和大家一起刷题&#xff0c;从今天开始小生也会开辟新的专栏。&#x1f61c;&#x1f61c;&#x1f61c; 目录 第一部分&#xff1a;题目描述 第二部分&#xff1a;题目分析 第三部分&#xff1a;解决方法 3.1 思路一&#xff1a;先排序…

LeetCode1005:K次取反后最大化的数组和

题目描述 给你一个整数数组 nums 和一个整数 k &#xff0c;按以下方法修改该数组&#xff1a; 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。 重复这个过程恰好 k 次。可以多次选择同一个下标 i 。 以这种方式修改数组后&#xff0c;返回数组 可能的最大和 。 解题思想 第…

AC自动机

AC自动机 AC自动机有一个很出色的功能&#xff1a;实现多模式匹配。 多模式匹配&#xff1a;模式串有多个&#xff0c;主串只有一个&#xff0c;要进行多次模式串匹配。如果用KMP就要一个一个模式串进行匹配&#xff0c;效率低。AC自动机就可以做到&#xff0c;只要经过一些预…

深度学习中权重初始化的重要性

深度学习模型中的权重初始化经常被人忽略&#xff0c;而事实上这是非常重要的一个步骤&#xff0c;模型的初始化权重的好坏关系到模型的训练成功与否&#xff0c;以及训练速度是否快速&#xff0c;效果是否更好等等&#xff0c;这次我们专门来看看深度学习中的权重初始化问题。…

RuoYi-Vue-Plus (SPEL 表达式)

RuoYi-Vue-Plus 中SPEL使用 DataScopeType 枚举类中&#xff1a; /*** 部门数据权限*/DEPT("3", " #{#deptName} #{#user.deptId} ", " 1 0 "), PlusDataPermissionHandler 拦截器中定义了解析器&#xff1a; buildDataFilter 方法中根据注解的…

基于电磁激励原理利用视触觉传感器估计抓取力矩的方法

由于触觉感知能使机器人通过其触觉传递获取丰富的接触信息&#xff0c;触觉感知已经成为机器人机械臂的一种流行的感知方式。而在触觉传感器可获取的各种信息中&#xff0c;通过外界接触从抓取物体传递到机器人手指的力矩等信息&#xff0c;在完成各种指令的实现尤为重要。如图…

数据结构––队列

1.队列的定义 2.队列的分类 2.1循环队 2.2链式队 3.队列的实现 3.1循环队 3.1.1声明 typedef int QDataType; #define MAXSIZE 50 //定义元素的最大个数 /*循环队列的顺序存储结构*/ typedef struct {QDataType *data;int front; //头指针int rear; //尾指针 }Queue;…

【最大公约数 排序】2344. 使数组可以被整除的最少删除次数

本文涉及知识点 最大公约数 排序 LeetCode2344. 使数组可以被整除的最少删除次数 给你两个正整数数组 nums 和 numsDivide 。你可以从 nums 中删除任意数目的元素。 请你返回使 nums 中 最小 元素可以整除 numsDivide 中所有元素的 最少 删除次数。如果无法得到这样的元素&a…

Apache中如何配置 ws 接口

Apache中如何配置 wss 接口 在Apache中配置WebSockets的支持&#xff0c;你需要使用mod_proxy_wstunnel模块&#xff0c;该模块是Apache的一个代理模块&#xff0c;它允许你代理WebSocket请求。 以下是配置步骤的简要说明和示例&#xff1a; 确保你的Apache服务器安装了mod_…

【Python小练】求斐波那契数列第n个数

题目 输出斐波那契数列第n个数。 分析 首先我们要知道&#xff0c;斐波那契数列&#xff0c;这个数列从第三位开始等于前两个数的和&#xff0c;要知道数列第n个数&#xff08;n>2&#xff09;&#xff0c;就要知道其前两相的值&#xff0c;着就需要用到递归了。来看一下吧…

【Java EE】多线程(二)Thread 类与常用方法

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 |《MySQL探索之旅》 |《Web世界探险家》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更…

【C++】:日期类的实现 -- 日期计算器

前言 1.日期类是一种十分经典的类型。对于C的初学者&#xff0c;它能够帮助我们融会贯通许多C的基础知识&#xff0c;它涉及许多的基础语法&#xff0c;比如引用&#xff0c;函数重载&#xff0c;传值/传参返回&#xff0c;构造函数&#xff0c;运算符重载&#xff0c;const成…

【Linux】详解core dump文件的作用以及用法ubuntu20.04下无法形成core dump文件的解决办法

说明 从第三大点开始讲解ubuntu20.04下无法形成core dump文件的解决办法。 一、core与term的区别 在之前讲过的信号中&#xff0c;终止进程的信号的动作分为两种&#xff0c;一种是core&#xff0c;一种是term。term&#xff08;全称termination&#xff09;是直接终止进程&am…

1084 外观数列(测试点3分析)

solution1 测试点3是n1的情况int转string&#xff1a;str to_string(i) string转int&#xff1a;i atoi(str.c_str()) #include<iostream> #include<string> using namespace std; int main(){int n, cnt;char x;string ans, t;cin >> t >> n;…

土壤侵蚀分布数据、土壤侵蚀强度、土壤类型分布、降水量分布、坡度坡向数据、植被覆盖度、土地利用数据、土壤质地分布

引言 土壤侵蚀是指土壤或成土母质在外力作用下被破坏剥蚀、搬运和沉积的过程。土壤侵蚀强度是根据土壤侵蚀的实际情况&#xff0c;按轻微、中度、严重等分为不同级别。中国是世界上土壤侵蚀最严重的国家之一&#xff0c;主要发生在黄河中上游黄土高原地区、长江中上游丘陵地区和…

综合性练习(后端代码练习3)——留言板

目录 一、准备工作 二、约定前后端交互接口 1、需求分析 2、接口定义 &#xff08;1&#xff09;发布留言 &#xff08;2&#xff09;获取留言 三、实现服务器代码 1、lombok介绍 &#xff08;1&#xff09;引入依赖 &#xff08;2&#xff09;使用lombok &#xff…

int类型的取值范围(为什么负数比正数表示的范围多一位)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;C语言基本概念 &#x1f337;追光的人&#xff0c;终会万丈光芒 目录 &#x1f3dd;1.int的基本概念&#xff1a; 空间大小&#xff1a; 有符号类型的表示形式&#xff1a; &#x1f3dd;2.…
最新文章