1. 程式人生 > >學習OpenCV範例(十八)——反向投影

學習OpenCV範例(十八)——反向投影

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

1、原理

影象的反向投影圖是用輸入影象的某一位置上畫素值(多維或灰度)對應在直方圖的一個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。

這個操作與前面介紹的LUT()方法非常類似,只不過是將LUT()引數中的查詢表改成直方圖而已。

2、程式碼實現

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

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


#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);
}

3、執行結果

 
            圖1、源影象                                    圖2、mask值

 

       圖3、h_s通道反向投影                   圖4、h通道反向投影

4、用到的類和函式

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') 是象素鄰域點的值。

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一樣

如果想看看更多的膚色檢測演算法可以點選下面這篇部落格