OpenCV實現漫水填充(待完善)

OpenCV實現漫水填充(待完善)


定理:

     用一種特定的顏色填充連通區域,通過設置可聯通像素的上下限以及連通方式來達到不同的填充效果的方法。

     自動選中和種子相連的區域,接着將該區域替換成指定的顏色。也可以用來從輸入圖像獲取掩碼區域,掩碼區域會加速區域,或只處理掩碼指定的像素點。


函數調用:

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

或者

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來填充。


算法實現:

       掃描線種子填充算法的基本過程如下:當給定種子點(x, y)時,首先分別向左和向右兩個方向填充種子點所在掃描線上的位於給定區域的一個區段,同時記下這個區段的範圍[xLeft, xRight],然後確定與這一區段相連通的上、下兩條掃描線上位於給定區域內的區段,並依次保存下來。反覆這個過程,直到填充結束。

掃描線種子填充算法可由下列四個步驟實現:

 

(1) 初始化一個空的棧用於存放種子點,將種子點(x, y)入棧;

 

(2) 判斷棧是否爲空,如果棧爲空則結束算法,否則取出棧頂元素作爲當前掃描線的種子點(x, y),y是當前的掃描線;

 

(3) 從種子點(x, y)出發,沿當前掃描線向左、右兩個方向填充,直到邊界。分別標記區段的左、右端點座標爲xLeft和xRight;

 

(4) 分別檢查與當前掃描線相鄰的y - 1和y + 1兩條掃描線在區間[xLeft, xRight]中的像素,從xLeft開始向xRight方向搜索,若存在非邊界且未填充的像素點,則找出這些相鄰的像素點中最右邊的一個,並將其作爲種子點壓入棧中,然後返回第(2)步;


實現代碼:
//實現漫水填充 在灰度圖的基礎上 彩色圖的色差不會計算 囧
//嘗試實現一下掃描填充法 
void SearchNewLine(InputArray src,stack<Point>& stack, int xleft, int xright, int y, Scalar seedPoint,Scalar newVal, Scalar loDiff, Scalar upDiff);

void method_one(InputOutputArray src, Point seedPoint, Scalar newVal, Scalar loDiff, Scalar upDiff)
{   //src爲原圖像 seedPoint爲種子,newVal爲設置的目標顏色,loDiff爲當前像素值與領域像素之差負值的max,
	//upDiff是之差正值的max

	std::stack<Point> stack;//利用堆棧來存儲
	stack.push(seedPoint);

	int xleft = 0;
	int xright = 0;//記錄邊界信息

	int rows = src.rows();
	int cols = src.cols();
	int channels = src.channels();

	Mat _src = src.getMat();//得到目標信息頭
	Scalar pointcurrent = _src.data[seedPoint.y *cols + seedPoint.x];
	int _pointcurrent = pointcurrent[0];//單通道

	while (!stack.empty())
	{
		Point current = stack.top();//取出棧頂元素,作爲當前循環的種子
		int x = current.x;
		int y = current.y;//(x,y)表示座標 x是橫座標 y是縱座標

		stack.pop();//刪除棧頂元素

		//向左填充 
		while(x>=0)
		{
			
				Scalar pointleft = _src.data[y*cols + x];
				int _pointleft = pointleft[0];
				int difference = _pointcurrent - _pointleft;
				if ((difference <= upDiff[0] && difference >= 0) || (difference >= -loDiff[0] && difference <= 0))
				{
					_src.data[y*cols + x] = newVal[0];
				}
				else
				{
					xleft = x;
					break;
				}
				x--;
		}

		x = current.x;//重新設置
		//向右填充
		while (x<=cols-1 )
		{
			if (x == cols - 1)//已經到達有邊界
			{
				xright = x;
				break;
			}
			else {
				Scalar pointright = _src.data[y*cols + (++x)];
				int _pointright = pointright[0];
				int difference = (_pointcurrent - _pointright);
				if ((difference <= upDiff[0] && difference >= 0) || (difference >= -loDiff[0] && difference <=0))
				{
					_src.data[y*cols + x] = newVal[0];
				}
				else
				{
					xright = x;//這裏重新設置了xleft xright的邊界值
					break;
				}
			}
		}
		
		//掃描上一行
		SearchNewLine(src,stack,xleft,xright,y-1,pointcurrent,newVal,loDiff,upDiff);
		//掃描下一行
		SearchNewLine(src,stack, xleft, xright, y +1,pointcurrent, newVal, loDiff, upDiff);
	}
}

void SearchNewLine(InputArray src,stack<Point>& stack,int xleft,int xright,int y,Scalar seedPoint,Scalar newVal,Scalar loDiff,Scalar upDiff)
{
	Mat _src = src.getMat();

	if ((y<0) || (y>_src.rows - 1))//處理越界的情況 縱座標
		return;

	if ((xleft <0 )|| (xright > _src.cols - 1))//橫座標越界
		return;


	int x = xleft;
	int targetx = x;
	bool flag = false;
	
	while (x <= xright)
	{
		Scalar current = _src.data[y*_src.cols + x];
		if ((((current[0] - seedPoint[0]) >= -loDiff[0]) && ((current[0] - seedPoint[0]) <= 0))
			|| (((current[0] - seedPoint[0]) <= upDiff[0]) && ((current[0] - seedPoint[0]) >= 0)))
		{
			if (current[0] != seedPoint[0])
			{
				targetx = x;
				flag = true;
			}
		}
		else
		{
			if (flag)
			{
				Point another = Point(targetx, y);
				stack.push(another);
				flag = false;
			}
		}
		x++;
	}

	//考慮邊界因素
	if (flag)
	{
		Point another = Point(targetx, y);
		stack.push(another);
		flag = false;
	}
	
	return;

}


int main()
{
	Mat srcimage = imread("C:\\Users\\l\\Desktop\\2.jpg",0);
	Mat srcimage1 = srcimage.clone();
	imshow("原始圖", srcimage);

	Rect ccomp;
	floodFill(srcimage1, Point(50, 50), 255, &ccomp, 10,10);
	imshow("漫水填充結果", srcimage1);//實現OpenCV自帶函數的漫水填充效果

	//調用掃描填充法
	Mat srcimage2 = srcimage.clone();
	method_one(srcimage2,Point(50,50),255,10,10);
	imshow("方法一的結果", srcimage2);
	
	waitKey(0);

	return 0;
}

實現效果:





結果相差較大,還需努力!!!如果有同道中人知道如何改進,還望給予指導!感激不盡!

參考鏈接:
http://blog.csdn.net/orbit/article/details/7343236

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章