1. 程式人生 > >SLIC超畫素分割演算法研究(程式碼可下載)

SLIC超畫素分割演算法研究(程式碼可下載)

超畫素概念是2003年Xiaofeng Ren[1]提出和發展起來的影象分割技術,是指具有相似紋理、顏色、亮度等特徵的相鄰畫素構成的有一定視覺意義的不規則畫素塊。它利用畫素之間特徵的相似性將畫素分組,用少量的超畫素代替大量的畫素來表達圖片特徵,很大程度上降低了影象後處理的複雜度,所以通常作為分割演算法的預處理步驟。已經廣泛用於影象分割、姿勢估計、目標跟蹤、目標識別等計算機視覺應用[2],而目前,OpenCV於超畫素生成,沒有發現網上有相關程式碼,但其實在opencv_contrib目錄下面的未穩定功能模組SLICSEEDSLSC演算法相關實現,如果想要使用這個目錄的功能,需要自己重新進行OpenCV

的編譯[8],有關編譯和配置方法可參考[8 ,9]。一些常用的超畫素分割演算法和效能對比如下表[3]:


本文介紹SLIC(simple lineariterativeclustering)超畫素分割演算法,即簡單的線性迭代聚類,該演算法是目前執行速度最快的超畫素分割方法[3],2015年實現並行執行速度達250FPS [4,5]。SLIC演算法是2010年Achanta[6]提出的一種思想簡單、實現方便的演算法,將彩色影象轉化為CIELAB顏色空間和XY座標下的5維特徵向量,然後對5維特徵向量構造距離度量標準,對影象畫素進行區域性聚類的過程[2],演算法基本思路與Kmeans聚類演算法類似。演算法具體實現過程如下[6,2]:


1.  初始化種子點(聚類中心):按照設定的超畫素個數,在影象內均勻的分配種子點。假設圖片總共有 N 個畫素點,預分割為 K 個相同尺寸的超畫素,那麼每個超畫素的大小為N/ K ,則相鄰種子點的距離(步長)近似為S=sqrt(N/K)。

2.  在種子點的n*n鄰域內重新選擇種子點(一般取n=3)。具體方法為:計算該鄰域內所有畫素點的梯度值,將種子點移到該鄰域內梯度最小的地方。這樣做的目的是為了避免種子點落在梯度較大的輪廓邊界上,以免影響後續聚類效果。

3.  在每個種子點周圍的鄰域內為每個畫素點分配類標籤(即屬於哪個聚類中心)。和標準的k-means在整張圖中搜索不同,SLIC的搜尋範圍限制為2S*2S,可以加速演算法收斂,如下圖。在此注意一點:期望的超畫素尺寸為S*S,但是搜尋的範圍是2S*2S。

4.  距離度量。包括顏色距離和空間距離。對於每個搜尋到的畫素點,分別計算它和該種子點的距離。距離計算方法如下:

其中,dc代表顏色距離,ds代表空間距離,Ns是類內最大空間距離,定義為Ns=S=sqrt(N/K),適用於每個聚類。最大的顏色距離Nc既隨圖片不同而不同,也隨聚類不同而不同,所以我們取一個固定常數m(取值範圍[1,40],一般取10)代替。最終的距離度量D'如下:


由於每個畫素點都會被多個種子點搜尋到,所以每個畫素點都會有一個與周圍種子點的距離,取最小值對應的種子點作為該畫素點的聚類中心。

5.  迭代優化。理論上上述步驟不斷迭代直到誤差收斂(可以理解為每個畫素點聚類中心不再發生變化為止),實踐發現10次迭代對絕大部分圖片都可以得到較理想效果,所以一般迭代次數取10。

6.  增強連通性。經過上述迭代優化可能出現以下瑕疵:出現多連通情況、超畫素尺寸過小,單個超畫素被切割成多個不連續超畫素等,這些情況可以通過增強連通性解決。主要思路是:新建一張標記表,表內元素均為-1,按照“Z”型走向(從左到右,從上到下順序)將不連續的超畫素、尺寸過小超畫素重新分配給鄰近的超畫素,遍歷過的畫素點分配給相應的標籤,直到所有點遍歷完畢為止。

SLIC演算法的程式碼(C++和matlab版)可到該演算法發明人Achanta的頁面[7]下載,我們下載該程式碼的C++windows版本,並編寫一個簡單影象格式轉換函式,將SLIC超畫素演算法與openCV一起實現了一個簡單的應用例項,因為SLIC演算法輸入必須是RGB彩色影象,因此我們考慮了灰度影象和彩色影象兩種不同情況下的資料轉換,當輸入為灰度圖時,將該灰度影象複製三次到其他色彩通道,主程式如下(程式碼執行還需要從Achanta網站[7]下載的SLIC.cpp和SLIC.h檔案,有關我們測試程式的所有程式碼(包括了SLIC.cpp和SLIC.h檔案)下載網站:http://download.csdn.net/detail/zhouxianen1987/9778247):

#if 1
#include <iostream>  
#include <time.h>
#include "opencv2/opencv.hpp" 
#include "SLIC.h"
using namespace std;
using namespace cv;
int imgOpenCV2SLIC(Mat img, int &height, int &width, int &dim, unsigned int * &image);
int imgSLIC2openCV(unsigned int *image, int height, int width, int dim, Mat &imgSLIC);
int main()
{

	Mat imgRGB;
	time_t tStart,tEnd,exeT;

	imgRGB= imread("0_0_77rgb.jpg");
	if (imgRGB.empty() == true){
		cout<<"can not open rgb image!"<<endl;
	}

	unsigned int *image; 

	int height; 
	int width; 
	int dim;
	long imgSize;
	
	int numlabels(0);
	SLIC slic;
	int m_spcount= 100 ;
	int m_compactness=10;
	imgOpenCV2SLIC(imgRGB,  height,  width,  dim, image);//OpenCV 影象資料轉換成SLIC影象資料
	imgSize = height* width;
	int* labels = new int[imgSize];

	tStart=clock();
	//SLIC超畫素分割,程式碼下載網站:http://ivrl.epfl.ch/research/superpixels#SLICO
	slic.DoSuperpixelSegmentation_ForGivenNumberOfSuperpixels(image, width, height, labels, numlabels, m_spcount, m_compactness);
	slic.DrawContoursAroundSegments(image, labels, width, height, 0);

	tEnd=clock();
	exeT=tEnd-tStart;

	Mat imgSLIC;

	imgSLIC2openCV(image, height,width,dim,imgSLIC);//SLIC結果:SLIC影象資料轉換成OpenCV 影象資料


	//結果顯示
	cout<<"SLIC執行時間exeT:"<<exeT<<"毫秒"<<endl;
	imshow("imgRGB",imgRGB);
	imshow("imgSLIC1",imgSLIC);
	waitKey();
	return 0;

}


//OpenCV Mat影象資料轉換為SLIC影象資料
//輸入:Mat img, int &height, int &width, int &dim,
//輸出:unsigned int * &image,同時返回轉換是否成功的標誌:成功為0,識別為1
int imgOpenCV2SLIC(Mat img, int &height, int &width, int &dim, unsigned int * &image)
{
	int error=0;
	if( img.empty() ) //請一定檢查是否成功讀圖 
	{ 
		error =1;
	} 

	dim=img.channels();//影象通道數目
	height=img.rows;
	width=img.cols;

	int imgSize=width*height;

	unsigned char *pImage  = new unsigned char [imgSize*4];
	if(dim==1){
		for(int j = 0; j < height; j++){
			uchar * ptr = img.ptr<uchar>(j);
			for(int i = 0; i < width; i++) {
				pImage[j * width*4 + 4*i+3] = 0;
				pImage[j * width*4 + 4*i+2] = ptr[0];
				pImage[j * width*4 + 4*i+1] = ptr[0];
				pImage[j * width*4 + 4*i] = ptr[0];		
				ptr ++;
			}
		}
	}
	else{
		if(dim==3){
			for(int j = 0; j < height; j++){
				Vec3b * ptr = img.ptr<Vec3b>(j);
				for(int i = 0; i < width; i++) {
					pImage[j * width*4 + 4*i+3] = 0;
					pImage[j * width*4 + 4*i+2] = ptr[0][2];//R
					pImage[j * width*4 + 4*i+1] = ptr[0][1];//G
					pImage[j * width*4 + 4*i]   = ptr[0][0];//B		
					ptr ++;
				}
			}
		}
		else  error=1;

	}

	image = new unsigned int[imgSize];
	memcpy( image, (unsigned int*)pImage, imgSize*sizeof(unsigned int) );
	delete pImage;

	return error;

}


//SLIC影象資料轉換為OpenCV Mat影象資料
//輸入:unsigned int *image, int height, int width, int dim
//輸出:Mat &imgSLIC ,同時返回轉換是否成功的標誌:成功為0,識別為1
int imgSLIC2openCV(unsigned int *image, int height, int width, int dim, Mat &imgSLIC)
{
	int error=0;//轉換是否成功的標誌:成功為0,識別為1

	if(dim==1){
		imgSLIC.create(height, width, CV_8UC1);
		//遍歷所有畫素,並設定畫素值 
		for( int j = 0; j< height; ++j) 
		{ 
			//獲取第 i行首畫素指標 
			uchar * p = imgSLIC.ptr<uchar>(j); 
			//對第 i行的每個畫素(byte)操作 
			for( int i = 0; i < width; ++i ) 
				p[i] =(unsigned char)(image[j*width+i]& 0xFF)  ; 
		} 
	}
	else{
		if(dim==3){
			imgSLIC.create(height, width, CV_8UC3);
			//遍歷所有畫素,並設定畫素值 
			for( int j = 0; j < height; ++j) 
			{ 
				//獲取第 i行首畫素指標 
				Vec3b * p = imgSLIC.ptr<Vec3b>(j); 
				for( int i = 0; i < width; ++i ) 
				{ 
					p[i][0] = (unsigned char)(image[j*width+i]          & 0xFF ); //Blue 
					p[i][1] = (unsigned char)((image[j*width+i] >>  8 ) & 0xFF ); //Green 
					p[i][2] = (unsigned char)((image[j*width+i] >>  16) & 0xFF ) ; //Red 
				} 
			}
		}
		else  error= 1 ;

	}

	return error;
}

#endif

執行效果如下:




參考資料:

[1]Ren X,Malik J.  Learning a classification model forsegmentation[C]/ /Proceedings of the IEEE International Conference on Computer  Vision.   Washington  DC, USA:   IEEE,2003:   10-17.[DOI:  10. 1109 /ICCV. 2003. 1238308]

[3]Achanta,Radhakrishna, et al. "SLICsuperpixels compared to state-of-the-artsuperpixel methods." PatternAnalysis and Machine Intelligence, IEEETransactions on 34.11 (2012): 2274-2282.

[5] gSLICr: SLIC superpixels at over 250Hz

[6] Achanta,Radhakrishna, et al. Slicsuperpixels. No. EPFL REPORT 149300. 2010.