codecamp

OpenCV后投影

目标

在本教程中,您将学习:

  • 什么是反投影和为什么它是有用的
  • 如何使用OpenCV函数cv :: calcBackProject来计算Back Projection
  • 如何通过使用OpenCV函数cv :: mixChannels来混合图像的不同通道

理论

什么是后投影?

  • 后投影是一种记录给定图像的像素适合直方图模型中像素分布的方式。
  • 为了使其更简单:对于Back Projection,您可以计算特征的直方图模型,然后使用它来在图像中查找此功能。
  • 应用示例:如果您具有肉色直方图(例如,色相饱和度直方图),则可以使用它来查找图像中的肉色区域:

它是如何工作的?

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

OpenCV后投影

T0

OpenCV后投影

T1

  • 现在,我们假设你会得到另一只手图像(Test Image),如下所示:(带有各自的直方图):

OpenCV后投影

OpenCV后投影

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

OpenCV后投影

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

  • 这个程序是做什么的?
  1. 加载图像
  2. 将原始转换为HSV格式,并将仅用于直方图的色相通道分开(使用OpenCV函数cv :: mixChannels
  3. 让用户在计算直方图时输入要使用的分组数。
  4. 计算直方图(如果纸箱更改则更新)和相同图像的反投影。
  5. 在窗口中显示反投影和直方图。
  • 可下载的代码:
  1. 点击这里查看基本版本(在本教程中解释)。
  2. 对于稍微优点的东西(使用HS直方图和floodFill来定义皮肤区域的面具),您可以检查改进的演示
  3. ...或者您可以随时查看样品中的经典camshiftdemo
  • 代码一览:

#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
Mat src; Mat hsv; Mat hue;
int bins = 25;
void Hist_and_Backproj(int, void* );
int main( int, char** argv )
{
  src = imread( argv[1], IMREAD_COLOR );
  if( src.empty() )
    { cout<<"Usage: ./calcBackProject_Demo1 <path_to_image>"<<endl;
      return -1;
    }
  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, WINDOW_AUTOSIZE );
  createTrackbar("* Hue  bins: ", window_image, &bins, 180, Hist_and_Backproj );
  Hist_and_Backproj(0, 0);
  imshow( window_image, src );
  waitKey(0);
  return 0;
}
void Hist_and_Backproj(int, void* )
{
  MatND hist;
  int histSize = MAX( bins, 2 );
  float hue_range[] = { 0, 180 };
  const float* ranges = { hue_range };
  calcHist( &hue, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false );
  normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );
  MatND backproj;
  calcBackProject( &hue, 1, 0, hist, backproj, &ranges, 1, true );
  imshow( "BackProj", backproj );
  int w = 400; int h = 400;
  int bin_w = cvRound( (double) w / histSize );
  Mat histImg = Mat::zeros( w, h, 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 ), -1 ); }
  imshow( "Histogram", histImg );
}

说明

  • 声明矩阵来存储我们的图像,并初始化我们的直方图使用的数据块数量:
Mat src; Mat hsv; Mat hue;
int bins = 25;
  • 读取输入图像并将其转换为HSV格式:
src = imread( argv[1], 1 );
cvtColor( src, hsv, COLOR_BGR2HSV );
  • 对于本教程,我们将仅使用Hue值作为我们的1-D直方图(如果要使用更标准的H-S直方图,可以获得更好的结果,请查看以上链接中的fancier代码):
hue.create(hsv.size(),hsv.depth());
int ch [] = {0,0};
mixChannels(&hsv,1,&hue,1,ch,1);

如你所见,我们使用函数cv :: mixChannels从hsv图像中只得到通道0(Hue)。它得到以下参数:

  1. **&hsv:**要从中复制通道的源数组
  2. 1:源数组的数量
  3. **&hue:**复制的频道的目标数组
  4. 1:目标数组的数量
  5. ch [] = {0,0}:指示通道如何复制的索引对数组。在这种情况下,&hsv的Hue(0)通道正被复制到0通道的&Hue(1通道),
  6. 1:索引对数
  • 创建一个跟踪栏,供用户输入bin值。Trackbar上的任何更改意味着调用Hist_and_Backproj回调函数。
char* window_image = "Source image";
namedWindow( window_image, WINDOW_AUTOSIZE );
createTrackbar("* Hue  bins: ", window_image, &bins, 180, Hist_and_Backproj );
Hist_and_Backproj(0, 0);
  • 显示图像并等待用户退出程序:
imshow( window_image, src );
waitKey(0);
return 0;
  • Hist_and_Backproj函数:初始化cv :: calcHist所需的参数。箱子数量来自Trackbar:
void Hist_and_Backproj(int,void *)
{
  MatND hist
  int histSize = MAX(bin,2);
  float hue_range [] = {0,180};
  const  float * ranges = {hue_range};
  • 计算直方图并将其归一化到范围[0,255]
calcHist( &hue, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false );
normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );
MatND backproj
calcBackProject(&hue,1,0,hist,backproj,&ranges,1,true);

所有参数都是已知的(与用于计算直方图相同),只有我们添加了backproj矩阵,这将保存源图像(&hue)的反投影,

  • 显示backproj:

imshow(“BackProj”,backproj);

  • 绘制图像的1-D色相直方图:

int w = 400; int h = 400;
int bin_w = cvRound( (double) w / histSize );
Mat histImg = Mat::zeros( w, h, 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 ), -1 ); }
imshow( "Histogram", histImg );

结果

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

OpenCV后投影

R0

OpenCV后投影

R1

OpenCV后投影

R2

OpenCV直方图比较
OpenCV模板匹配
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

OpenCV教程

OpenCV高级GUI和媒体(highgui模块)

OpenCV图像输入和输出(imgcodecs模块)

对象检测(objdetect模块)

计算摄影(照片模块)

图像拼接(拼接模块)

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }