漫水填充例項詳解
思想
漫水填充:就是將與種子點相連線的區域換成特定的顏色,通過設定連通方式或畫素的範圍可以控制填充的效果。通常是用來標記或分離影象的一部分對其進行處理或分析,或者通過掩碼來加速處理過程。可以只處理掩碼指定的部分或者對掩碼上的區域進行遮蔽不處理。
主要作用就是:選出與種子點連通的且顏色相近的點,對畫素點的值進行處理。如果遇到掩碼,根據掩碼進行處理。
工作流程:
- 選定種子點(x,y)
- 檢查種子點的顏色,如果該點顏色與周圍連線點的顏色不相同,則將周圍點顏色設定為該點顏色,如果相同則不做處理。但是周圍點不一定都會變成和種子點的顏色相同,如果周圍連線點在給定的範圍內(lodiff - updiff)內或在種子點的象素範圍內才會改變顏色。
- 檢測其他連線點,進行2步驟的處理,直到沒有連線點,即到達檢測區域邊界停止。
例項
下面貼出毛星雲的例子,進行詳細分析
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
using namespace std;
using namespace cv;
Mat g_srcImage, g_dstImage, g_grayImage, g_maskImage; //定義原始圖,目標圖,灰度圖,掩模圖(進一步控制那些區域將被填充顏色)
int g_nFillMode = 1; //漫水填充的模式
int g_nLowDifference = 20, g_nUpDifference = 20; //負差最大值,正差最大值
int g_nConnectivity = 4; //表示floodFill函式識別符號低8位的連通值,4,只考慮畫素水平和垂直方向的連線點
int g_bIsColor = true; //是否為彩色圖的識別符號布林值
bool g_bUseMask = false; //是否顯示掩模視窗的布林值
int g_nNewMaskVal = 255; //新的重新繪製的畫素值
//滑鼠回撥函式
static void onMouse(int event, int x, int y, int, void *)
{
//若滑鼠左鍵沒有按下,便返回
if (event != EVENT_LBUTTONDOWN)
return;
//呼叫floodFill函式之前的引數準備
Point seed = Point(x, y); //漫水填充的起始點
int LowDifference = g_nFillMode == 0 ? 0 : g_nLowDifference; //空範圍的漫水填充,此值設為0,否則設為全域性的g_nLowDifference
int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference; //空範圍的漫水填充,此值設為0,否則設為全域性的g_nUpDifference
//識別符號的0-7位為g_nConnectivity,8-15位為g_nNewMaskVal左移8位的值,16-23位為CV_FLOODFILL_FIXED_RANGE或者0
//低8位用於控制演算法的連通性,可取4(填充演算法只考慮當前享受水平方向和垂直方向)/8(還考慮對角線方向),
//高8位可為0/FLOODFILL_FIXED_RANGE(考慮當前畫素與種子畫素之間的差)/FLOODFILL_MASK_ONLY(不填充改變原始影象,去填充掩模影象)
//中間8位制定填充掩碼影象的值,
int flags = g_nConnectivity + (g_nNewMaskVal << 8) + (g_nFillMode == 1 ? FLOODFILL_FIXED_RANGE : 0);
//隨機生成bgr值
int b = (unsigned)theRNG() & 255;
int g = (unsigned)theRNG() & 255;
int r = (unsigned)theRNG() & 255;
//重繪區域的最小邊界矩陣區域
Rect ccomp;
//重繪區域享受的新值,若為彩色圖,取Scalar(b,g,r),若為灰度圖,取Scalar(r * 0.299 + g * 0.587 + b * 0.114)
//bgr是隨機的
Scalar newVal = g_bIsColor ? Scalar(b, g, r) : Scalar(r * 0.299 + g * 0.587 + b * 0.114);
Mat dst = g_bIsColor ? g_dstImage : g_grayImage; //目標圖賦值
int area;
//正式呼叫floodFill函式
if (g_bUseMask)
{
threshold(g_maskImage, g_maskImage, 1, 128, THRESH_BINARY);
area = floodFill(dst, g_maskImage, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference), Scalar(UpDifference, UpDifference, UpDifference), flags);
imshow("mask", g_maskImage);
}
else
{
area = floodFill(dst, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference), Scalar(UpDifference, UpDifference, UpDifference), flags);
}
imshow("效果圖", dst);
cout << area << "個畫素被重繪\n" << endl;
}
int main()
{
g_srcImage = imread("C://Users/yangping/Desktop/dog.jpg");
g_srcImage.copyTo(g_dstImage);
cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY); //將image0從三通道轉換到灰度圖
g_maskImage.create(g_srcImage.rows + 2, g_srcImage.cols + 2, CV_8UC1); //用image0的尺寸初始化掩模mask
namedWindow("效果圖", WINDOW_AUTOSIZE);
//建立Trackbar
createTrackbar("負差最大值", "效果圖", &g_nLowDifference, 255, 0);
createTrackbar("正差最大值", "效果圖", &g_nUpDifference, 255, 0);
//滑鼠回撥函式
setMouseCallback("效果圖", onMouse, 0);
//迴圈輪詢按鍵
while (1)
{
//先顯示效果圖
imshow("效果圖", g_bIsColor ? g_dstImage : g_grayImage);
//獲取按鍵
int c = waitKey(0);
//判斷esc是否按下 若按下便退出
if ((c & 255) == 27)
{
cout << "程式退出" << endl;
break;
}
//根據案件不同 進行各種操作
switch ((char) c)
{
//按"1",效果圖在灰度圖,彩色圖之間相互轉換
case '1':
if (g_bIsColor) //原來為彩色圖,轉換為灰色圖,並將掩模mask所有原始設為0
{
cout << "鍵盤“1”被按下,切換彩色/灰度模式" << endl;
cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
g_maskImage = Scalar::all(0); //將所有元素設定為0
g_bIsColor = false; //將識別符號置為false,表示當前影象為灰度
}
else //若原來為灰度值,將彩圖image0再複製給image,病將mask設為0
{
cout << "鍵盤“1”被按下,切換彩色/灰度模式" << endl;
g_srcImage.copyTo(g_dstImage);
g_maskImage = Scalar::all(0);
g_bIsColor = true; //將識別符號設為true,表示影象模式為彩色
}
break;
//按“2”,顯示/隱藏掩模視窗
case '2':
if (g_bUseMask)
{
destroyWindow("mask");
g_bUseMask = false;
}
else
{
namedWindow("mask", 0);
g_maskImage = Scalar::all(0);
imshow("mask", g_maskImage);
g_bUseMask = true;
}
break;
//按“3”,恢復原始影象
case '3':
cout << "按3,恢復原始影象" << endl;
g_srcImage.copyTo(g_dstImage);
cvtColor(g_dstImage, g_grayImage, COLOR_BGR2GRAY);
g_maskImage = Scalar::all(0);
break;
//按“4”,使用空範圍的漫水填充
case '4':
cout << "按4,使用空範圍漫水填充" << endl;
g_nFillMode = 0;
break;
//按“5”,使用漸變,固定範圍的漫水填充
case '5':
cout << "按5,使用漸變,固定的漫水填充" << endl;
g_nFillMode = 1;
break;
//按“6”,使用漸變,浮動範圍的漫水填充
case '6':
cout << "按6,使用漸變,浮動範圍的漫水填充" << endl;
g_nFillMode = 2;
break;
//按7,操作識別符號的低8位使用4位的連結模式
case '7':
cout << "按7,操作識別符號的低8位使用4位的連結模式" << endl;
g_nConnectivity = 4;
break;
//按8,識別符號的低8位使用8位的連結模式
case '8':
cout << "按8,識別符號的低8位使用8位的連結模式" << endl;
g_nConnectivity = 8;
break;
default:
break;
}
}
return 0;
}
1
int floodFill(InputOutputArray image, InputOutputArray mask, Point seedPoint, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )
這裡只對帶掩碼的版本進行分析。所謂掩碼,其實就是提取感興趣的區域對其進行各種處理,如遮蔽作用,特徵提取,單獨處理等。
函式引數:
- Iimage:輸入影象,可以是一通道或者是三通道。
- mask:該版本特有的掩膜。 單通道,8位,在長寬上都比原影象image多2個畫素點。漫水填充不會填充掩膜區域的非0畫素點,所以說掩膜是遮蔽了漫水填充的處理。如邊緣檢測運算元的輸出可以用來作為掩膜,這樣可防止邊緣區域不被填充。因為掩膜比原影象大,所以掩膜中的(x,y),對應的原影象的畫素點為(x+1,y+1)。
- seedPoint:漫水填充的種子點,即起始點。
- newVal:被填充的畫素點新的值
- rect:設定函式將要重繪區域的最小邊界矩形區域
- loDiff:表示當前的觀察點畫素值與其相鄰區域畫素值或待加入該區域的畫素之間的亮度或顏色之間負差的最大值。
- upDiff:表示當前的觀察點畫素值與其相鄰區域畫素值或待加入該區域的畫素之間的亮度或顏色之間負差的最小值。
flags:操作位識別符號,包括三個部分
1.低八位(0-7):控制演算法的連通性,設定為4:填充演算法只考慮當前畫素點垂直和水平方向;設定為8:除垂直和水平方向,還會考慮對角線的相鄰點
2.高八位(16-23):可以為0或者下列兩種識別符號組合。FLOODFILL_FIXED_RANGE:考慮種子畫素與種子畫素之間的差,否則考慮當前畫素與與鄰近畫素的差。
FLOODFILL_MASK_ONLY :如果設定這個識別符號,函式不會填充或改變原始影象(也就是忽略的newVal),而是去填充掩膜影象。
3.中間八位:用於指定填充掩碼影象的值,如果flags中間八位值為0,則掩碼會用1填充
函式功能
從起始的種子點開始,用指定顏色填充與其相連的畫素。連通性取決於相鄰畫素的顏色和亮度,畫素在下列情況下被屬於重新繪製的區域:
在灰度和浮動區域的情況下:
在灰度和固定區域的情況下:
和:
在彩色和浮動變化區域情況下:
和:
在彩色多通道和固定區域情況下,src(x’,y’)是該部件內已知的相鄰畫素的值。也就是說,加入到該連線的部件中的畫素的顏色和亮度應該足夠接近:
- 在浮動範圍內,該值已經屬於連線元件中相鄰畫素點的顏色和亮度。
- 在固定範圍內,有種子點的顏色和亮度。
函式可以原地標記制定顏色的連線元件,也可以建立一個掩碼,然後提取輪廓,或者將該區域複製到另一個影象上等等。