反向投影圖

本次要講的範例是反向投影,反向投影若是是按照字面上的理解,還有書本上的理解可能會比較困難,可是若是是舉一些具體的簡單的例子,那可能就比較容易接受了,應用的話,能夠檢測出膚色區域,例如,你有一個膚色直方圖 ( Hue-Saturation 直方圖 ),你能夠用它來尋找圖像中的膚色區域,如今咱們來看看反向變換吧。ios

一、原理

圖像的反向投影圖是用輸入圖像的某一位置上像素值(多維或灰度)對應在直方圖的一個bin上的值來代替該像素值,因此獲得的反向投影圖是單通的。 算法

舉個小例數組

(1)例如灰度圖像以下 
Image=函數

  0    1    2    3 
  4    5    6    7 
  8    9   10   11 
  8    9   14   15 
(2)該灰度圖的直方圖爲(bin指定的區間爲[0,3),[4,7),[8,11),[12,16)) 
Histogram=學習


  4    4    6    2 
(3)反向投影圖 
Back_Projection= 
  4    4    4    4 
  4    4    4    4 
  6    6    6    6 
  6    6    2    2 
例如位置(0,0)上的像素值爲0,對應的bin爲[0,3),因此反向直方圖在該位置上的值這個bin的值4。 ui

這個操做與前面介紹的LUT()方法很是相似,只不過是將LUT()參數中的查找表改爲直方圖而已。spa

二、代碼實現

①、代碼運用了floodfull()函數,點擊圖片的位置,獲得填充的聯通圖,賦值給mask,計算mask所對應圖片部位的直方圖,再對圖片進行反向投影。.net

②、對圖片提取hue通道值,再進行反向投影,顯示結果。code

#include "stdafx.h"


/**
 * @file BackProject_Demo2.cpp
 * @brief Sample code for backproject function usage ( a bit more elaborated )
 * @author OpenCV team
 */

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"

#include <iostream>

using namespace cv;
using namespace std;

/// Global Variables
Mat src; Mat hsv;Mat hand;Mat hue;Mat hand1;
Mat mask;

int lo = 20; int up = 20;int bins=25;
const char* window_image = "Source image";

/// Function Headers
void Hist_and_Backproj( );
void pickPoint (int event, int x, int y, int, void* );
void hue_and_Backproj(int, void* );

/**
 * @function main
 */
int main( int, char** argv )
{
  /// Read the image
  src = imread( "hand_sample2.jpg", 1 );
  /// Transform it to HSV
  cvtColor( src, hsv, COLOR_BGR2HSV );
  /// 分離 Hue 通道
  hue.create( hsv.size(), hsv.depth() );
  int ch[] = { 0, 0 };
  mixChannels( &hsv, 1, &hue, 1, ch, 1 );

  hand.create(src.size(),src.type());
  hand1.create(src.size(),src.type());

  /// Show the image
  namedWindow( window_image, WINDOW_AUTOSIZE );
  imshow( window_image, src );
  /// Set Trackbars for floodfill thresholds
 
  createTrackbar("* Hue  bins: ", window_image, &bins, 180, hue_and_Backproj );
  createTrackbar( "Low thresh", window_image, &lo, 255, 0 );
  createTrackbar( "High thresh", window_image, &up, 255, 0 );
  /// Set a Mouse Callback
  hue_and_Backproj(0,0);
  setMouseCallback( window_image, pickPoint, 0 );

  waitKey(0);
  return 0;
}

/**
 * @function pickPoint
 */
void pickPoint (int event, int x, int y, int, void* )
{
  if( event != CV_EVENT_LBUTTONDOWN )
    { return; }
  Point seed = Point( x, y );

  int newMaskVal = 255;
  Scalar newVal = Scalar( 120, 120, 120 );

  int connectivity = 8;
  int flags = connectivity + (newMaskVal << 8 ) + FLOODFILL_FIXED_RANGE + FLOODFILL_MASK_ONLY;

  Mat mask2 = Mat::zeros( src.rows + 2, src.cols + 2, CV_8UC1 );
  floodFill( src, mask2, seed, newVal, 0, Scalar( lo, lo, lo ), Scalar( up, up, up), flags );
  mask = mask2( Range( 1, mask2.rows - 1 ), Range( 1, mask2.cols - 1 ) );

  imshow( "Mask", mask );

  Hist_and_Backproj( );
}

/**
 * @function Hist_and_Backproj
 */
void Hist_and_Backproj( )
{
  MatND hist;
  hand = Scalar::all(0);
  int h_bins = 30; int s_bins = 32;
  int histSize[] = { h_bins, s_bins };

  float h_range[] = { 0, 179 };
  float s_range[] = { 0, 255 };
  const float* ranges[] = { h_range, s_range };

  int channels[] = { 0, 1 };

  /// Get the Histogram and normalize it
  calcHist( &hsv, 1, channels, mask, hist, 2, histSize, ranges, true, false );

  normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );

  /// Get Backprojection
  Mat backproj;
  calcBackProject( &hsv, 1, channels, hist, backproj, ranges, 1, true );

  /// Draw the backproj
  imshow( "BackProj", backproj );
  src.copyTo(hand,backproj);
  imshow("hand",hand);
}

void hue_and_Backproj(int, void* )
{
  MatND hist;
  hand1 = Scalar::all(0);
  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 );

  threshold(backproj,backproj,254,255,CV_THRESH_TOZERO);
  /// 顯示反向投影
  imshow( "BackProj1", backproj );
  src.copyTo(hand1,backproj);
  imshow("hand1",hand1);
}

三、運行結果

 
            圖一、源圖像                                    圖二、mask值orm

 

       圖三、h_s通道反向投影                   圖四、h通道反向投影

四、用到的類和函數

mixChannels

功能:從輸入中拷貝某通道到輸出中特定的通道。

結構:

void mixChannels(const Mat*src, size_t nsrcs, Mat* dst, size_t ndsts, const int* fromTo, size_t npairs)

src:一系列輸入圖像的數組, 被拷貝的通道的來源一系列輸入圖像的數組, 被拷貝的通道的來源

nsrcs:輸入圖像的個數

dst:一系列目的圖像的數組, 儲存拷貝的通道,全部的數組必須事先分配空間(如用create),大小和深度須與輸入數組等同。

ndsts:目的數組中圖像的數目

fromTo:通道索引對的數組,指示如何將輸入圖像的某一通道拷貝到目的圖像的某一通道。偶數下標的用來標識輸入矩陣,奇數下標的用來標識輸出矩陣。若是偶數下標爲負數,那麼相應的輸出矩陣爲零矩陣。

npairs:fromTo中的序號對數(兩個算1對)。

floodFill

功能:用指定顏色填充一個鏈接域

結構:

int floodFill(InputOutputArray image, InputOutputArray mask, Point seed, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )

image:輸入的 1- 或 3-通道, 8-比特或浮點數圖像。輸入的圖像將被函數的操做所改變,除非你選擇 CV_FLOODFILL_MASK_ONLY 選項 

mask:運算掩碼, 應該是單通道、8-比特圖像, 長和寬上都比輸入圖像 image 大兩個象素點。若非空,則函數使用且更新掩碼, 因此使用者需對 mask 內容的初始化負責。填充不會通過 mask 的非零象素, 例如,一個邊緣檢測子的輸出能夠用來做爲 mask 來阻止填充邊緣。或者有可能在屢次的函數調用中使用同一個 mask ,以保證填充的區域不會重疊。注意: 由於 mask 比欲填充圖像大,因此 mask 中與輸入圖像(x,y)像素點相對應的點具備(x+1,y+1)座標。mask能夠被用爲輸入值(此時他控制能夠被填充的區域),也能夠做爲輸出值(此時他只已經被填充的區域)。

seed:開始填充的點

newVal:新的從新繪製的象素值

rect:可選輸出參數,返回重繪區域最小邊界矩形

loDiff:當前觀察象素值與其部件領域象素或者待加入該部件的種子象素之負差(Lower difference)的最大值。(具體看後面公式表達)

upDiff:當前觀察象素值與其部件領域象素或者待加入該部件的種子象素之正差(upper difference)的最大值。(具體看後面公式表達)

flag:操做選項. 低8位(第0-7位)比特包含連通值, 4 (缺省) 或 8, 若是爲4,填充算法只考慮當前像素水平方向和垂直方向的相鄰點;若是爲8,除上述相鄰點外,還會包括對角方向的相鄰點。在函數執行連經過程中肯定使用哪一種鄰域方式。高8位(第16-23位)比特能夠是 0 或下面的開關選項的組合: 
CV_FLOODFILL_FIXED_RANGE - 若是設置,則考慮當前象素與種子象素之間的差,不然考慮當前象素與其相鄰象素的差。(範圍是浮點數). 
CV_FLOODFILL_MASK_ONLY - 若是設置,函數不填充原始圖像 (忽略 new_val), 但填充掩模圖像 (這種狀況下 MASK 必須是非空的).

中間比特位(第8-15位)指定填充掩碼的像素值。若是中間比特值爲0,則掩碼將用1填充,此時掩碼將會顯示爲全黑的圖像,全部的flags能夠經過OR操做鏈接起來。例如,若是想用8領域填充,並填充固定像素值範圍,是填充掩碼二不是填充源圖像,以及填充值爲47,那麼輸入的參數應該是:

flags=8|CV_FLOODFILL_MASK_ONLY|CV_FLOODFILL_FIXED_RANGE|(47<<8);

函數 cvFloodFill 用指定顏色,從種子點開始填充一個連通域。連通性由象素值的接近程度來衡量。在點 (x, y) 的象素被認爲是屬於從新繪製的區域,若是: 
src(x',y')-lo_diff<=src(x,y)<=src(x',y')+up_diff, 灰度圖像,浮動範圍 
src(seed.x,seed.y)-lo<=src(x,y)<=src(seed.x,seed.y)+up_diff, 灰度圖像,固定範圍 
src(x',y')r-lo_diffr<=src(x,y)r<=src(x',y')r+up_diffr 和 
src(x',y')g-lo_diffg<=src(x,y)g<=src(x',y')g+up_diffg 和 
src(x',y')b-lo_diffb<=src(x,y)b<=src(x',y')b+up_diffb, 彩色圖像,浮動範圍 
src(seed.x,seed.y)r-lo_diffr<=src(x,y)r<=src(seed.x,seed.y)r+up_diffr 和 
src(seed.x,seed.y)g-lo_diffg<=src(x,y)g<=src(seed.x,seed.y)g+up_diffg 和 
src(seed.x,seed.y)b-lo_diffb<=src(x,y)b<=src(seed.x,seed.y)b+up_diffb, 彩色圖像,固定範圍 
其中 src(x',y') 是象素鄰域點的值。 

你們若是想更加了解floodfill參數究竟是什麼,如何變化的,能夠經過 http://blog.csdn.net/xiaowei_cqu/article/details/8987387 這篇博客的代碼本身運行如下,理解如下。

同時也能夠查閱學習OpenCV這本書。

calcBackProject

功能:計算反向投影

結構:

void calcBackProject(const Mat* arrays, int narrays, const int* channels, InputArray hist, OutputArray backProject, const float** ranges, double scale=1, bool uniform=true )

arrays:源圖像,他們有着一樣的depth( CV_8U  、  CV_32F)一樣的size,能夠是任意的通道數。

narrays:源圖像的數目

channels:用於計算反投影的通道列表。

hist:輸入的直方圖

backProject:輸出單通道反投影圖像,和arrays[0]有一樣的size和depth

ranges:和calchist中的ranges同樣

若是想看看更多的膚色檢測算法能夠點擊下面這篇博客

http://blog.csdn.net/onezeros/article/details/6342567 

相關文章
相關標籤/搜索