Opencv學習筆記之十二——漫水填充法(floodFill)
首先感謝@淺墨_毛星雲,本篇博文是小武通過學習@淺墨_毛星雲的部落格以及書籍《opencv3.0程式設計入門》整理的筆記及疑問心得,小武水平有限,歡迎交流。
定義:
漫水填充法是一種用特定的顏色填充聯通區域,通過設定可連通畫素的上下限以及連通方式來達到不同的填充效果的方法。漫水填充經常被用來標記或分離影象的一部分以便對其進行進一步處理或分析,也可以用來從輸入影象獲取掩碼區域,掩碼會加速處理過程,或只處理掩碼指定的畫素點,操作的結果總是某個連續的區域。
漫水填充法的基本思想
所謂漫水填充,簡單來說,就是自動選中了和種子點相連的區域,接著將該區域替換成指定的顏色,這是個非常有用的功能,經常用來標記或者分離影象的一部分進行處理或分析.漫水填充也可以用來從輸入影象獲取掩碼區域,掩碼會加速處理過程,或者只處理掩碼指定的畫素點.
以此填充演算法為基礎,類似photoshop的魔術棒選擇工具就很容易實現了。漫水填充(FloodFill)是查詢和種子點聯通的顏色相同的點,魔術棒選擇工具則是查詢和種子點聯通的顏色相近的點,將和初始種子畫素顏色相近的點壓進棧作為新種子
在OpenCV中,漫水填充是填充演算法中最通用的方法。且在OpenCV 2.X中,使用C++重寫過的FloodFill函式有兩個版本。一個不帶掩膜mask的版本,和一個帶mask的版本。這個掩膜mask,就是用於進一步控制哪些區域將被填充顏色(比如說當對同一影象進行多次填充時)。這兩個版本的FloodFill,都必須在影象中選擇一個種子點,然後把臨近區域所有相似點填充上同樣的顏色,不同的是,不一定將所有的鄰近畫素點都染上同一顏色,漫水填充操作的結果總是某個連續的區域。當鄰近畫素點位於給定的範圍(從loDiff到upDiff)內或在原始seedPoint畫素值範圍內時,FloodFill函式就會為這個點塗上顏色。
函式詳解
在OpenCV中,漫水填充演算法由floodFill函式實現,其作用是用我們指定的顏色從種子點開始填充一個連線域。連通性由畫素值的接近程度來衡量。OpenCV2.X有兩個C++重寫版本的floodFill。
第一版本
C++: int floodFill(InputOutputArray image, Point seedPoint, Scalar newVal, Rect* rect=0, Scalar lo Diff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )
第二版本
C++: int floodFill(InputOutputArray image,
InputOutputArray mask,
Point seedPoint,
Scalar newVal,
Rect* rect=0,
Scalar loDiff=Scalar(),
Scalar upDiff=Scalar(),
int flags=4 )
引數詳解:
- 第一個引數,InputOutputArray型別的image, 輸入/輸出1通道或3通道,8位或浮點影象,具體引數由之後的引數具體指明。
- 第二個引數, InputOutputArray型別的mask,這是第二個版本的floodFill獨享的引數,表示操作掩模,。它應該為單通道、8位、長和寬上都比輸入影象 image 大兩個畫素點的影象。第二個版本的floodFill需要使用以及更新掩膜,所以這個mask引數我們一定要將其準備好並填在此處。需要注意的是,漫水填充不會填充掩膜mask的非零畫素區域。例如,一個邊緣檢測運算元的輸出可以用來作為掩膜,以防止填充到邊緣。同樣的,也可以在多次的函式呼叫中使用同一個掩膜,以保證填充的區域不會重疊。另外需要注意的是,掩膜mask會比需填充的影象大,所以 mask 中與輸入影象(x,y)畫素點相對應的點的座標為(x+1,y+1)。
- 第三個引數,Point型別的seedPoint,漫水填充演算法的起始點。
- 第四個引數,Scalar型別的newVal,畫素點被染色的值,即在重繪區域畫素的新值。
- 第五個引數,Rect*型別的rect,有預設值0,一個可選的引數,用於設定floodFill函式將要重繪區域的最小邊界矩形區域。
- 第六個引數,Scalar型別的loDiff,有預設值Scalar( ),表示當前觀察畫素值與其部件鄰域畫素值或者待加入該部件的種子畫素之間的亮度或顏色之負差(lower brightness/color difference)的最大值。
- 第七個引數,Scalar型別的upDiff,有預設值Scalar( ),表示當前觀察畫素值與其部件鄰域畫素值或者待加入該部件的種子畫素之間的亮度或顏色之正差(lower brightness/color difference)的最大值。
- 第八個引數,int型別的flags,操作標誌符,此引數包含三個部分,比較複雜,詳細參見這裡和官方文件。
程式碼實現:
//新增標頭檔案
#include<opencv/cxcore.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
using namespace std;
using namespace cv;
static void help()
{
cout << "Welcom to floodFill" << endl;
cout << "Hot keys: \n"
"\tESC - quit the program\n"
"\tc - switch color/grayscale mode\n"
"\tm - switch mask mode\n"
"\tr - restore the original image\n"
"\ts - use null-range floodfill\n"
"\tf - use gradient floodfill with fixed(absolute) range\n"
"\tg - use gradient floodfill with floating(relative) range\n"
"\t4 - use 4-connectivity mode\n"
"\t8 - use 8-connectivity mode\n" << endl;
}
Mat image0, image, gray, mask;//定義原始圖、目標圖、灰度圖、掩模圖
int ffillMode = 1;//漫水填充的模式
int loDiff = 20, upDiff = 20;//負差最大值、正差最大值
int connectivity = 4;//表示floodFill函式識別符號低八位的連通值
int isColor = true;//是否為彩色圖的識別符號布林值
bool useMask = false;//是否顯示掩膜視窗的布林值
int newMaskVal = 255;//新的重新繪製的畫素值
static void onMouse( int event, int x, int y, int, void* )
{
// 若滑鼠左鍵沒有按下,便返回
if( event != EVENT_LBUTTONDOWN )
return;
Point seed = Point(x,y);
int lo = ffillMode == 0 ? 0 : loDiff;//空範圍的漫水填充,此值設為0,
//否則設為全域性的g_nLowDifference
int up = ffillMode == 0 ? 0 : upDiff;//空範圍的漫水填充,此值設為0,
//否則設為全域性的g_nUpDifference
int flags = connectivity + (newMaskVal << 8) +
(ffillMode == 1 ? FLOODFILL_FIXED_RANGE : 0);//識別符號的0~7位為g_nConnectivity,
//8~15位為g_nNewMaskVal左移8位的值,16~23位為CV_FLOODFILL_FIXED_RANGE或者0。
//隨機生成bgr值
int b = (unsigned)theRNG() & 255;//隨機返回一個0~255之間的值
int g = (unsigned)theRNG() & 255;
int r = (unsigned)theRNG() & 255;
Rect ccomp;//定義重繪區域的最小邊界矩形區域
Scalar newVal = isColor ? Scalar(b, g, r) : Scalar(r*0.299 + g*0.587 + b*0.114);//在重繪區域畫素的新值,
//若是彩色圖模式,取Scalar(b, g, r);若是灰度圖模式,取Scalar(r*0.299 + g*0.587 + b*0.114)
Mat dst = isColor ? image : gray;//目標圖的賦值
int area;
if( useMask )
{
threshold(mask, mask, 1, 128, THRESH_BINARY);
area = floodFill(dst, mask, seed, newVal, &ccomp, Scalar(lo, lo, lo),
Scalar(up, up, up), flags);
imshow( "mask", mask );
}
else
{
area = floodFill(dst, seed, newVal, &ccomp, Scalar(lo, lo, lo),
Scalar(up, up, up), flags);
}
imshow("image", dst);
cout << area << " pixels were repainted\n";
}
int main( int argc, char** argv )
{
image0 = imread("home.jpg", 1);
help();
image0.copyTo(image);
cvtColor(image0, gray, COLOR_BGR2GRAY);
mask.create(image0.rows+2, image0.cols+2, CV_8UC1);
namedWindow( "image", 0 );
createTrackbar( "lo_diff", "image", &loDiff, 255, 0 );
createTrackbar( "up_diff", "image", &upDiff, 255, 0 );
setMouseCallback( "image", onMouse, 0 );
while(1)
{
imshow("image", isColor ? image : gray);
char c =(char) waitKey(0);
if( c == 27 )
{
cout << "Exiting ...\n";
break;
}
switch( c )
{
case 'c':
if( isColor )
{
cout << "Grayscale mode is set\n";
cvtColor(image0, gray, COLOR_BGR2GRAY);
mask = Scalar::all(0);
isColor = false;
}
else
{
cout << "Color mode is set\n";
image0.copyTo(image);
mask = Scalar::all(0);
isColor = true;
}
break;
case 'm':
if( useMask )
{
destroyWindow( "mask" );
useMask = false;
}
else
{
namedWindow( "mask", 0 );
mask = Scalar::all(0);
imshow("mask", mask);
useMask = true;
}
break;
case 'r':
cout << "Original image is restored\n";
image0.copyTo(image);
cvtColor(image, gray, COLOR_BGR2GRAY);
mask = Scalar::all(0);
break;
case 's':
cout << "Simple floodfill mode is set\n";
ffillMode = 0;
break;
case 'f':
cout << "Fixed Range floodfill mode is set\n";
ffillMode = 1;
break;
case 'g':
cout << "Gradient (floating range) floodfill mode is set\n";
ffillMode = 2;
break;
case '4':
cout << "4-connectivity mode is set\n";
connectivity = 4;
break;
case '8':
cout << "8-connectivity mode is set\n";
connectivity = 8;
break;
}
}
return 0;
}
效果: