什么是反向投影:
直方图反向投影的步骤:
1. In each pixel of our Test Image (i.e. p(i; j) ), collect the data and find the correspondent bin location for
that pixel (i.e. (hi;j; si;j) ).对测试图像中的每个像素 ( ),获取色调数据并找到该色调(
)在直方图中的bin的位置。
4. Applying the steps above, we get the following BackProjection image for our Test Image
5. In terms of statistics, the values stored in BackProjection represent the probability that a pixel in Test
Image belongs to a skin area, based on the model histogram that we use. For instance in our Test image,
the brighter areas are more probable to be skin area (as they actually are), whereas the darker areas have
less probability (notice that these “dark” areas belong to surfaces that have some shadow on it, which in
turns affects the detection).使用统计学的语言, BackProjection 中储存的数值代表了测试图像中该像素属于皮肤区域的 概率
mixChannels: 从输入中拷贝某通道到输出中特定的通道。
C++: void mixChannels(const Mat*src, size_t nsrcs, Mat* dst, size_t ndsts, const int* fromTo, size_t npairs)
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
using namespace cv;
using namespace std;
/// Global Variables
Mat src; Mat hsv; Mat hue;
int bins = 25;
/// Function Headers
void Hist_and_Backproj(int, void* );
/**
* @function main
*/
int main( int, char** argv )
{
/// Read the image
src = imread( argv[1], 1 );
/// Transform it to HSV
cvtColor( src, hsv, COLOR_BGR2HSV );
/// Use only the Hue value
hue.create( hsv.size(), hsv.depth() );
int ch[] = { 0, 0 };
mixChannels( &hsv, 1, &hue, 1, ch, 1 );
/// Create Trackbar to enter the number of bins
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);
/// Show the image
imshow( window_image, src );
/// Wait until user exits the program
waitKey(0);
return 0;
}
/**
* @function Hist_and_Backproj
* @brief Callback to Trackbar
*/
void Hist_and_Backproj(int, void* )
{
MatND hist;
int histSize = MAX( bins, 2 );
float hue_range[] = { 0, 180 };
const float* ranges = { hue_range };
/// Get the Histogram and normalize it
calcHist( &hue, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false );
normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );
/// Get Backprojection
MatND backproj;
calcBackProject( &hue, 1, 0, hist, backproj, &ranges, 1, true );
/// Draw the backproj
imshow( "BackProj", backproj );
/// Draw the histogram
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(i)*h/255.0 ) ), Scalar( 0, 0, 255 ), -1 ); }
imshow( "Histogram", histImg );
}
汉语翻译版:http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/histograms/back_projection/back_projection.html
(高级)使用 H-S 直方图和 floodFill(漫水填充) 来定义皮肤区域的掩码
参考:http://www.tuicool.com/articles/6FJvAb
一、引言 · 水漫填充的定义
漫水填充法是一种用特定的颜色填充联通区域,通过设置可连通像素的上下限以及连通方式来达到不同的填充效果的方法。漫水填充经常被用来标记或分离图像的一部分以便对其进行进一步处理或分析,也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或只处理掩码指定的像素点,操作的结果总是某个连续的区域。
二、漫水填充法的基本思想
所谓漫水填充,简单来说,就是自动选中了和种子点相连的区域,接着将该区域替换成指定的颜色,这是个非常有用的功能,经常用来标记或者分离图像的一部分进行处理或分析.漫水填充也可以用来从输入图像获取掩码区域,掩码会加速处理过程,或者只处理掩码指定的像素点.
以此填充算法为基础,类似photoshop的魔术棒选择工具就很容易实现了。漫水填充(FloodFill)是查找和种子点联通的颜色相同的点,魔术棒选择工具则是查找和种子点联通的颜色相近的点,将和初始种子像素颜色相近的点压进栈作为新种子
在OpenCV中,漫水填充是填充算法中最通用的方法。且在OpenCV 2.X中,使用C++重写过的FloodFill函数有两个版本。一个不带掩膜mask的版本,和一个带mask的版本。这个掩膜mask,就是用于进一步控制哪些区域将被填充颜色(比如说当对同一图像进行多次填充时)。这两个版本的FloodFill,都必须在图像中选择一个种子点,然后把临近区域所有相似点填充上同样的颜色,不同的是,不一定将所有的邻近像素点都染上同一颜色,漫水填充操作的结果总是某个连续的区域。当邻近像素点位于给定的范围(从loDiff到upDiff)内或在原始seedPoint像素值范围内时,FloodFill函数就会为这个点涂上颜色。
在OpenCV中,漫水填充算法由floodFill函数实现,其作用是用我们指定的颜色从种子点开始填充一个连接域。连通性由像素值的接近程度来衡量。OpenCV2.X有两个C++重写版本的floodFill。
第一个版本的floodFill :
int floodFill(InputOutputArray image, Point seedPoint, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )
第二个版本的floodFill:
int floodFill(InputOutputArray image, InputOutputArray mask, Point seedPoint,Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )
下面是一起介绍的参数详解。除了第二个参数外,其他的参数都是共用的。
而所有flags可以用or操作符连接起来,即“|”。例如,如果想用8邻域填充,并填充固定像素值范围,填充掩码而不是填充源图像,以及设填充值为38,那么输入的参数是这样:
flags=8 | FLOODFILL_MASK_ONLY | FLOODFILL_FIXED_RANGE | (38<<8)
#include
#include
using namespace cv;
using namespace std;
Mat src, hsv, mask;
int low = 20; int up = 20;
void get_mask(int event, int x, int y, int, void*);
void hist_and_backproj();
int main()
{
src = imread("hand.jpg");
if (!src.data)
{
cout << "load image error!" << endl;
return -1;
}
cvtColor(src, hsv, CV_BGR2HSV);
char *windowname = "sourse image";
namedWindow(windowname, CV_WINDOW_AUTOSIZE);
imshow(windowname, src);
createTrackbar("low", windowname, &low, 255, 0);
createTrackbar("up", windowname, &up, 255, 0);
setMouseCallback(windowname,get_mask, 0);
waitKey(0);
return 0;
}
void get_mask(int event, int x, int y, int, void*)
{
if (event != EVENT_LBUTTONDOWN)
return;
Point seed = Point(x, y);
Scalar newval = Scalar(120, 120, 120);
int connectivity = 8;
int newMaskval = 255;
Mat mask2 = Mat::zeros(src.rows + 2, src.cols + 2, CV_8UC1);
int flag = connectivity + (newMaskval << 8) + FLOODFILL_FIXED_RANGE + FLOODFILL_MASK_ONLY;
floodFill(src, mask2, seed, newval, 0, Scalar(low, low, low), Scalar(up, up, up), flag);
mask = mask2(Range(1, mask2.rows - 1), Range(1, mask2.cols - 1));
imshow("mask", mask);
hist_and_backproj();
}
void hist_and_backproj()
{
MatND hist;
int h_bins = 30, s_bins = 32;
int histSize[] = { h_bins, s_bins };
float h_ranges[] = { 0, 179 };
float s_ranges[] = { 0, 255 };
const float* ranges[] = { h_ranges, s_ranges };
int channels[] = { 0, 1 };
calcHist(&hsv,1, channels, mask, hist, 2, histSize, ranges, true, false);
normalize(hist, hist, 0, 255, NORM_MINMAX, -1, Mat());
MatND backproj;
calcBackProject(&hsv, 1, channels, hist, backproj, ranges, 1, true);
imshow("proj", backproj);
}