【opencv入門】漫水填充算法(Floodfill)
一、引言 · 漫水填充的定義
漫水填充法是一種用特定的顏色填充聯通區域,通過設置可連通像素的上下限以及連通方式來達到不同的填充效果的方法。漫水填充經常被用來標記或分離圖像的一部分以便對其進行進一步處理或分析,也可以用來從輸入圖像獲取掩碼區域,掩碼會加速處理過程,或只處理掩碼指定的像素點,操作的結果總是某個連續的區域。
二、漫水填充法的基本思想
所謂漫水填充,簡單來說,就是自動選中了和種子點相連的區域,接著將該區域替換成指定的顏色,這是個非常有用的功能,經常用來標記或者分離圖像的一部分進行處理或分析。漫水填充也可以用來從輸入圖像獲取掩碼區域,掩碼會加速處理過程,或者只處理掩碼指定的像素點。
以此填充算法為基礎,類似photoshop的魔術棒選擇工具就很容易實現了。漫水填充(FloodFill)是查找和種子點聯通的顏色相同的點,魔術棒選擇工具則是查找和種子點聯通的顏色相近的點,將和初始種子像素顏色相近的點壓進棧作為新種子。
在OpenCV中,漫水填充是填充算法中最通用的方法。且在OpenCV 2.X中,使用C++重寫過的FloodFill函數有兩個版本。一個不帶掩膜mask的版本,和一個帶mask的版本。這個掩膜mask,就是用於進一步控制哪些區域將被填充顏色(比如說當對同一圖像進行多次填充時)。這兩個版本的FloodFill,都必須在圖像中選擇一個種子點,然後把臨近區域所有相似點填充上同樣的顏色,不同的是,不一定將所有的鄰近像素點都染上同一顏色,漫水填充操作的結果總是某個連續的區域。當鄰近像素點位於給定的範圍(從loDiff到upDiff)內或在原始seedPoint像素值範圍內時,FloodFill函數就會為這個點塗上顏色。
三、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 )
參數詳解:
第一個參數,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,操作標誌符,此參數包含三個部分,比較復雜,我們一起詳細看看。
低八位(第0~7位)用於控制算法的連通性,可取4 (4為缺省值) 或者 8。如果設為4,表示填充算法只考慮當前像素水平方向和垂直方向的相鄰點;如果
設為 8,除上述相鄰點外,還會包含對角線方向的相鄰點。
高八位部分(16~23位)可以為0 或者如下兩種選項標識符的組合:
FLOODFILL_FIXED_RANGE - 如果設置為這個標識符的話,就會考慮當前像素與種子像素之間的差,否則就考慮當前像素與其相鄰像素的差。也就
是說,這個範圍是浮動的。
FLOODFILL_MASK_ONLY - 如果設置為這個標識符的話,函數不會去填充改變原始圖像 (也就是忽略第三個參數newVal), 而是去填充掩模圖像
(mask)。這個標識符只對第二個版本的floodFill有用,因第一個版本裏面壓根就沒有mask參數。
中間八位部分,上面關於高八位FLOODFILL_MASK_ONLY標識符中已經說的很明顯,需要輸入符合要求的掩碼。Floodfill的flags參數的中間八位的值就
是用於指定填充掩碼圖像的值的。但如果flags中間八位的值為0,則掩碼會用1來填充。
而所有flags可以用or操作符連接起來,即“|”。例如,如果想用8鄰域填充,並填充固定像素值範圍,填充掩碼而不是填充源圖像,以及設填充值為38,那麽輸入的參數是這樣:
flags=8 | FLOODFILL_MASK_ONLY | FLOODFILL_FIXED_RANGE | (38<<8)
【示例】
1 //漫水填充
2 #include <opencv2/opencv.hpp>
3 #include <opencv2/imgproc/imgproc.hpp>
4
5 using namespace cv;
6
7 int main( )
8 {
9 Mat src = imread("1.jpg");
10 imshow("【原始圖】",src);
11 Rect ccomp;
12 floodFill(src, Point(50,300), Scalar(155, 255,55), &ccomp, Scalar(20, 20, 20),Scalar(20, 20, 20));
13 imshow("【效果圖】",src);
14 imwrite("效果圖.jpg", src);
15 waitKey(0);
16 return 0;
17 }
效果展示:
四、關於SetMouseCallback函數
因為下面示例程序中有用到SetMouseCallback函數,我們在這裏講一講。SetMouseCallback函數為指定的窗口設置鼠標回調函數。
C++: void setMouseCallback(conststring& winname, MouseCallback onMouse, void* userdata=0 )
第一個參數,const string&類型的winname,為窗口的名字。
第二個參數,MouseCallback類型的onMouse,指定窗口裏每次鼠標時間發生的時候,被調用的函數指針。這個函數的原型應該為
voidFoo(int event, int x, int y, int flags, void* param);其中event是 CV_EVENT_*變量之一, x和y
是鼠標指針在圖像坐標系的坐標(不是窗口坐標系), flags是CV_EVENT_FLAG的組合, param是用戶定義的傳遞到
cvSetMouseCallback函數調用的參數。
第三個參數,void*類型的userdata,用戶定義的傳遞到回調函數的參數,有默認值0。
五、綜合示例
1 //漫水填充,綜合示例
2 #include "opencv2/imgproc/imgproc.hpp"
3 #include "opencv2/highgui/highgui.hpp"
4 #include <iostream>
5
6 using namespace cv;
7 using namespace std;
8
9 Mat g_srcImage, g_dstImage, g_grayImage, g_maskImage;//定義原始圖、目標圖、灰度圖、掩模圖
10 int g_nFillMode = 1;//漫水填充的模式
11 int g_nLowDifference = 20, g_nUpDifference = 20;//負差最大值、正差最大值
12 int g_nConnectivity = 4;//表示floodFill函數標識符低八位的連通值
13 int g_bIsColor = true;//是否為彩色圖的標識符布爾值
14 bool g_bUseMask = false;//是否顯示掩膜窗口的布爾值
15 int g_nNewMaskVal = 255;//新的重新繪制的像素值
16
17 static void ShowHelpText()
18 {
19 //輸出一些幫助信息
20 cout << "\n\n\n\t歡迎來到漫水填充示例程序~\n\n";
21 cout << "\n\n\t按鍵操作說明: \n\n"
22 "\t\t鼠標點擊圖中區域- 進行漫水填充操作\n"
23 "\t\t鍵盤按鍵【ESC】- 退出程序\n"
24 "\t\t鍵盤按鍵【1】- 切換彩色圖/灰度圖模式\n"
25 "\t\t鍵盤按鍵【2】- 顯示/隱藏掩膜窗口\n"
26 "\t\t鍵盤按鍵【3】- 恢復原始圖像\n"
27 "\t\t鍵盤按鍵【4】- 使用空範圍的漫水填充\n"
28 "\t\t鍵盤按鍵【5】- 使用漸變、固定範圍的漫水填充\n"
29 "\t\t鍵盤按鍵【6】- 使用漸變、浮動範圍的漫水填充\n"
30 "\t\t鍵盤按鍵【7】- 操作標誌符的低八位使用4位的連接模式\n"
31 "\t\t鍵盤按鍵【8】- 操作標誌符的低八位使用8位的連接模式\n"
32 "\n\n\t\t\t\t\t\t\t\tn\n\n";
33 }
34
35 static void onMouse( int event, int x, int y, int, void* )
36 {
37 // 若鼠標左鍵沒有按下,便返回
38 if( event != CV_EVENT_LBUTTONDOWN )
39 return;
40
41 Point seed = Point(x,y);
42 int LowDifference = g_nFillMode == 0 ? 0 : g_nLowDifference;//空範圍的漫水填充,此值設為0,否則設為全局的g_nLowDifference
43 int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference;//空範圍的漫水填充,此值設為0,否則設為全局的g_nUpDifference
44 int flags = g_nConnectivity + (g_nNewMaskVal << 8) +
45 (g_nFillMode == 1 ? CV_FLOODFILL_FIXED_RANGE : 0);//標識符的0~7位為g_nConnectivity,8~15位為g_nNewMaskVal左移8位的值,16~23位為CV_FLOODFILL_FIXED_RANGE或者0。
46
47 //隨機生成bgr值
48 int b = (unsigned)theRNG() & 255;//隨機返回一個0~255之間的值
49 int g = (unsigned)theRNG() & 255;//隨機返回一個0~255之間的值
50 int r = (unsigned)theRNG() & 255;//隨機返回一個0~255之間的值
51 Rect ccomp;//定義重繪區域的最小邊界矩形區域
52
53 Scalar newVal = g_bIsColor ? 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)
54
55 Mat dst = g_bIsColor ? g_dstImage : g_grayImage;//目標圖的賦值
56 int area;
57
58 if( g_bUseMask )
59 {
60 threshold(g_maskImage, g_maskImage, 1, 128, CV_THRESH_BINARY);
61 area = floodFill(dst, g_maskImage, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference),
62 Scalar(UpDifference, UpDifference, UpDifference), flags);
63 imshow( "mask", g_maskImage );
64 }
65 else
66 {
67 area = floodFill(dst, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference),
68 Scalar(UpDifference, UpDifference, UpDifference), flags);
69 }
70
71 imshow("效果圖", dst);
72
73 cout << area << " 個像素被重繪\n";
74 }
75
76 int main( int argc, char** argv )
77 {
78 //載入原圖
79 g_srcImage = imread("1.jpg", 1);
80
81 //if( !g_srcImage.data ) { printf("Oh,no,讀取圖片image0錯誤~! \n"); return false; }
82
83 //顯示幫助文字
84 ShowHelpText();
85
86 g_srcImage.copyTo(g_dstImage);//拷貝源圖到目標圖
87 cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);//轉換三通道的image0到灰度圖
88 g_maskImage.create(g_srcImage.rows+2, g_srcImage.cols+2, CV_8UC1);//利用image0的尺寸來初始化掩膜mask
89
90 namedWindow( "效果圖",CV_WINDOW_AUTOSIZE );
91
92 //創建Trackbar
93 createTrackbar( "負差最大值", "效果圖", &g_nLowDifference, 255, 0 );
94 createTrackbar( "正差最大值" ,"效果圖", &g_nUpDifference, 255, 0 );
95
96 //鼠標回調函數
97 setMouseCallback( "效果圖", onMouse, 0 );
98
99 //循環輪詢按鍵
100 while(1)
101 {
102 //先顯示效果圖
103 imshow("效果圖", g_bIsColor ? g_dstImage : g_grayImage);
104
105 //獲取鍵盤按鍵
106 int c = waitKey(0);
107 //判斷ESC是否按下,若按下便退出
108 if( (c & 255) == 27 )
109 {
110 cout << "程序退出...........\n";
111 break;
112 }
113
114 //根據按鍵的不同,進行各種操作
115 switch( (char)c )
116 {
117 //如果鍵盤“1”被按下,效果圖在在灰度圖,彩色圖之間互換
118 case ‘1‘:
119 if( g_bIsColor )//若原來為彩色,轉為灰度圖,並且將掩膜mask所有元素設置為0
120 {
121 cout << "鍵盤“1”被按下,切換彩色/灰度模式,當前操作為將【彩色模式】切換為【灰度模式】\n";
122 cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
123 g_maskImage = Scalar::all(0); //將mask所有元素設置為0
124 g_bIsColor = false; //將標識符置為false,表示當前圖像不為彩色,而是灰度
125 }
126 else//若原來為灰度圖,便將原來的彩圖image0再次拷貝給image,並且將掩膜mask所有元素設置為0
127 {
128 cout << "鍵盤“1”被按下,切換彩色/灰度模式,當前操作為將【彩色模式】切換為【灰度模式】\n";
129 g_srcImage.copyTo(g_dstImage);
130 g_maskImage = Scalar::all(0);
131 g_bIsColor = true;//將標識符置為true,表示當前圖像模式為彩色
132 }
133 break;
134 //如果鍵盤按鍵“2”被按下,顯示/隱藏掩膜窗口
135 case ‘2‘:
136 if( g_bUseMask )
137 {
138 destroyWindow( "mask" );
139 g_bUseMask = false;
140 }
141 else
142 {
143 namedWindow( "mask", 0 );
144 g_maskImage = Scalar::all(0);
145 imshow("mask", g_maskImage);
146 g_bUseMask = true;
147 }
148 break;
149 //如果鍵盤按鍵“3”被按下,恢復原始圖像
150 case ‘3‘:
151 cout << "按鍵“3”被按下,恢復原始圖像\n";
152 g_srcImage.copyTo(g_dstImage);
153 cvtColor(g_dstImage, g_grayImage, COLOR_BGR2GRAY);
154 g_maskImage = Scalar::all(0);
155 break;
156 //如果鍵盤按鍵“4”被按下,使用空範圍的漫水填充
157 case ‘4‘:
158 cout << "按鍵“4”被按下,使用空範圍的漫水填充\n";
159 g_nFillMode = 0;
160 break;
161 //如果鍵盤按鍵“5”被按下,使用漸變、固定範圍的漫水填充
162 case ‘5‘:
163 cout << "按鍵“5”被按下,使用漸變、固定範圍的漫水填充\n";
164 g_nFillMode = 1;
165 break;
166 //如果鍵盤按鍵“6”被按下,使用漸變、浮動範圍的漫水填充
167 case ‘6‘:
168 cout << "按鍵“6”被按下,使用漸變、浮動範圍的漫水填充\n";
169 g_nFillMode = 2;
170 break;
171 //如果鍵盤按鍵“7”被按下,操作標誌符的低八位使用4位的連接模式
172 case ‘7‘:
173 cout << "按鍵“7”被按下,操作標誌符的低八位使用4位的連接模式\n";
174 g_nConnectivity = 4;
175 break;
176 //如果鍵盤按鍵“8”被按下,操作標誌符的低八位使用8位的連接模式
177 case ‘8‘:
178 cout << "按鍵“8”被按下,操作標誌符的低八位使用8位的連接模式\n";
179 g_nConnectivity = 8;
180 break;
181 }
182 }
183
184 return 0;
185 }
【opencv入門】漫水填充算法(Floodfill)