Opencv圖像識別從零到精通(24)------漫水填充,種子填充,區域生長、孔洞填充


         可以說從這篇文章開始,就結束了圖像識別的入門基礎,來到了第二階段的學習。在平時處理二值圖像的時候,除了要進行形態學的一些操作,還有有上一節講到的輪廓連通區域的面積周長標記等,還有一個最常見的就是孔洞的填充,opencv這裏成爲漫水填充,其實也可以叫種子填充,或者區域生長,基本的原理是一樣的,但是應用的時候需要注意一下,種子填充用遞歸的辦法,回溯算法,漫水填充使用堆棧,提高效率,同時還提供了一種方式是掃描行。經常用來填充孔洞,現在來具體看看。


漫水填充:也就是用一定顏色填充聯通區域,通過設置可連通像素的上下限以及連通方式來達到不同的填充效果;漫水填充經常被用來標記或分離圖像的一部分以便對其進行進一步處理或分析,也可以用來從輸入圖像獲取掩碼區域,掩碼會加速處理過程,或只處理掩碼指定的像素點,操作的結果總是某個連續的區域。簡單的說,就是選中點seedPoint,然後選取出它周圍和它色彩差異不大的點,並將它們的值改爲newVal。如果被選取的點,遇到mask掩碼,則放棄對該方向的

種子填充算法,種子填充算法是從多邊形區域內部的一點開始,由此出發找到區域內的所有像素。種子填充算法採用的邊界定義是區域邊界上所有像素具有某個特定的顏色值,區域內部所有像素均不取這一特定顏色,而邊界外的像素則可具有與邊界相同的顏色值。

具體算法步驟:

  • 標記種子(x,y)的像素點 ;
  • 檢測該點的顏色,若他與邊界色和填充色均不同,就用填充色填   充該點,否則不填充 ;
  • 檢測相鄰位置,繼續 2。這個過程延續到已經檢測區域邊界範圍內的所有像素爲止。
  • 當然在搜索的時候有兩種檢測相鄰像素:四向連通和八向連通。四向連通即從區域上一點出發,通過四個方向上、下、左、右來檢索。而八向連通加上了左上、左下、右上、右下四個方向。這種算法的有點是算法簡單,易於實現,也可以填充帶有內孔的平面區域。但是此算法需要更大的存儲空間以實現棧結構,同一個像素多次入棧和出棧,效率低,運算量大。

掃描線種子填充算法:該算法屬於種子填充算法,它是以掃描線上的區段爲單位操作。所謂區段,就是一條掃描線上相連着的若干內部象素的集合。掃描線種子填充算法思想:首先填充當前掃描線上的位於給定區域的一區段,然後確定於這一區段相鄰的上下兩條線上位於該區域內是否存在需要填充的新區段,如果存在,則依次把他們保存起來,反覆這個過程,直到所保存的各區段都填充完畢。

<span style="font-size:18px;">FloodFill函數
  C++: int floodFill(InputOutputArray image, InputOutputArray mask, 
                Point seedPoint, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 );
  InputOutputArray:輸入和輸出圖像。
  mask:            輸入的掩碼圖像。
  seedPoint:      算法開始處理的開始位置。 
  newVal:         圖像中所有被算法選中的點,都用這個數值來填充。
    rect:            最小包圍矩陣。
  loDiff:         最大的低亮度之間的差異。
    upDiff:         最大的高亮度之間的差異。
  flag:           選擇算法連接方式。
</span>

根據上面的函數先看一個基礎的應用

<span style="font-size:18px;">#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;

Mat g_srcImage, g_dstImage, g_grayImage, g_maskImage;
int g_nFillMode = 1;
int g_nLowDifference = 20, g_nUpDifference = 20;
int g_nConnectivity = 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;
	Point seed = Point(x,y);
	int LowDifference = g_nFillMode == 0 ? 0 : g_nLowDifference;
	int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference;
	int flags = g_nConnectivity + (g_nNewMaskVal << 8) +(g_nFillMode == 1 ? FLOODFILL_FIXED_RANGE : 0);
	int b = (unsigned)theRNG() & 255;
	int g = (unsigned)theRNG() & 255;
	int r = (unsigned)theRNG() & 255;
	Rect ccomp;
	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;
	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";
}

int main( int argc, char** argv )
{

	g_srcImage = imread("lena.jpg", 1);

	if( !g_srcImage.data ) { printf("讀取圖片image0錯誤~! \n"); return false; } 

	g_srcImage.copyTo(g_dstImage);
	cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
	g_maskImage.create(g_srcImage.rows+2, g_srcImage.cols+2, CV_8UC1);
	namedWindow( "效果圖",WINDOW_AUTOSIZE );
	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 << "程序退出...........\n";
			break;
		}

		//根據按鍵的不同,進行各種操作
		switch( (char)c )
		{
			//如果鍵盤“1”被按下,效果圖在在灰度圖,彩色圖之間互換
		case '1':
			if( g_bIsColor )//若原來爲彩色,轉爲灰度圖,並且將掩膜mask所有元素設置爲0
			{
				cout << "鍵盤“1”被按下,切換彩色/灰度模式,當前操作爲將【彩色模式】切換爲【灰度模式】\n";
				cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
				g_maskImage = Scalar::all(0);	//將mask所有元素設置爲0
				g_bIsColor = false;	//將標識符置爲false,表示當前圖像不爲彩色,而是灰度
			}
			else//若原來爲灰度圖,便將原來的彩圖image0再次拷貝給image,並且將掩膜mask所有元素設置爲0
			{
				cout << "鍵盤“1”被按下,切換彩色/灰度模式,當前操作爲將【彩色模式】切換爲【灰度模式】\n";
				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”被按下,恢復原始圖像\n";
			g_srcImage.copyTo(g_dstImage);
			cvtColor(g_dstImage, g_grayImage, COLOR_BGR2GRAY);
			g_maskImage = Scalar::all(0);
			break;
			//如果鍵盤按鍵“4”被按下,使用空範圍的漫水填充
		case '4':
			cout << "按鍵“4”被按下,使用空範圍的漫水填充\n";
			g_nFillMode = 0;
			break;
			//如果鍵盤按鍵“5”被按下,使用漸變、固定範圍的漫水填充
		case '5':
			cout << "按鍵“5”被按下,使用漸變、固定範圍的漫水填充\n";
			g_nFillMode = 1;
			break;
			//如果鍵盤按鍵“6”被按下,使用漸變、浮動範圍的漫水填充
		case '6':
			cout << "按鍵“6”被按下,使用漸變、浮動範圍的漫水填充\n";
			g_nFillMode = 2;
			break;
			//如果鍵盤按鍵“7”被按下,操作標誌符的低八位使用4位的連接模式
		case '7':
			cout << "按鍵“7”被按下,操作標誌符的低八位使用4位的連接模式\n";
			g_nConnectivity = 4;
			break;
			//如果鍵盤按鍵“8”被按下,操作標誌符的低八位使用8位的連接模式
		case '8':
			cout << "按鍵“8”被按下,操作標誌符的低八位使用8位的連接模式\n";
			g_nConnectivity = 8;
			break;
		}
	}

	return 0;
}
</span>
                                         

再來看看漫水填充在填充孔洞上的應用

#include<opencv2\core\core.hpp>
#include<opencv2\highgui\highgui.hpp>
#include<opencv2\imgproc\imgproc.hpp>
using namespace std;
using namespace cv;  
void chao_fillHole(const cv::Mat srcimage, cv::Mat &dstimage)  
{  
    Size m_Size = srcimage.size();  
    Mat temimage = Mat::zeros(m_Size.height + 2, m_Size.width + 2, srcimage.type());   
    srcimage.copyTo(temimage(Range(1, m_Size.height + 1), Range(1, m_Size.width + 1)));  
    floodFill(temimage, Point(0, 0), Scalar(255));  
    Mat cutImg;
    temimage(Range(1, m_Size.height + 1), Range(1, m_Size.width + 1)).copyTo(cutImg);  
    dstimage = srcimage | (~cutImg);  
}  
int main()  
{  
    Mat src=imread("111.png");
	Mat dst;
   chao_fillHole(src, dst)  ;
   imshow("tianchong",dst);
   waitKey(0);
   return 0;

}  
                                                                           

matlab

<span style="font-size:18px;">I=imread('tire.tif');
figure,imshow(I)
BW=imfill(I);
figure,imshow(BW)</span>


圖像識別算法交流 QQ羣:145076161,歡迎圖像識別與圖像算法,共同學習與交流


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