1.圖像分割的兩條思路
場景分割時機器視覺中的重要任務,尤其對家庭機器人而言,優秀的場景分割算法是實現復雜功能的基礎。但是大家搞了幾十年也還沒搞定——不是我說的,是接下來要介紹的這篇論文說的。圖像分割的搞法大概有兩種:劍宗——自低向上:先將圖像聚類成小的像素團再慢慢合並,氣宗——自頂向下:用多尺度模板分割圖像,再進一步將圖像優化分割成不同物體。當然,還有將二者合而為一的方法:training with data set. 這第三種方法也不好,太依賴於已知的物體而失去了靈活性。家庭機器人面對家里越來越多的東西需要一種非訓練且效果很好的分割法。 Object Partitioning using Local Convexity 一文的作者從古籍中(也不老,1960s左右吧),找到了一種基於凹凸性的分割方法。實際上基於凹凸的圖像理解在之前是被研究過的,但是隨着神經網絡的出現,漸漸這種從明確物理意義入手的圖像"理解"方法就被淹沒了。對於二維圖像而言,其凹凸性較難描述,但對於三維圖像而言,凹凸幾乎是與生俱來的性質。
2.LCCP方法
LCCP是Locally Convex Connected Patches的縮寫,翻譯成中文叫做 ”局部凸連接打包一波帶走“~~~算法大致可以分成兩個部分:1.基於超體聚類的過分割。2.在超體聚類的基礎上再聚類。超體聚類作為一種過分割方法,在理想情況下是不會引入錯誤信息的,也就是說適合在此基礎上再進行處理。關於超體聚類相關內容見我的博文:超體聚類。 LCCP方法並不依賴於點雲顏色,所以只使用空間信息和法線信息,wc=0。ws=1,wn=4。
2.1算法理論
點雲完成超體聚類之后,對於過分割的點雲需要計算不同的塊之間凹凸關系。凹凸關系通過 CC(Extended Convexity Criterion) 和 SC (Sanity criterion)判據來進行判斷。其中 CC 利用相鄰兩片中心連線向量與法向量夾角來判斷兩片是凹是凸。顯然,如果圖中a1>a2則為凹,反之則為凸。
考慮到測量噪聲等因素,需要在實際使用過程中引入門限值(a1需要比a2大出一定量)來濾出較小的凹凸誤判。此外,為去除一些小噪聲引起的誤判,還需要引入“第三方驗證”,如果某塊和相鄰兩塊都相交,則其凹凸關系必相同。CC 判據最終如CCe:
如果相鄰兩面中,有一個面是單獨的,cc判據是無法將其分開的。舉個簡單的例子,兩本厚度不同的書並排放置,視覺算法應該將兩本書分割開。如果是台階,則視覺算法應該將台階作為一個整體。本質上就是因為厚度不同的書存在surface-singularities。為此需要引入SC判據,來對此進行區分。
如圖所示,相鄰兩面是否真正聯通,是否存在單獨面,與θ角有關,θ角越大,則兩面真的形成凸關系的可能性就越大。據此,可以設計SC判據:
其中S(向量)為兩平面法向量的叉積。
最終,兩相鄰面之間凸邊判據為:
在標記完各個小區域的凹凸關系后,則采用區域增長算法將小區域聚類成較大的物體。此區域增長算法受到小區域凹凸性限制,既:
只允許區域跨越凸邊增長。
至此,分割完成,在濾去多余噪聲后既獲得點雲分割結果。此外:考慮到RGB-D圖像隨深度增加而離散,難以確定八叉樹尺寸,故在z方向使用對數變換以提高精度。分割結果如圖:
從圖中可知,糾纏在一起,顏色形狀相近的物體完全被分割開了,如果是圖像分割要達到這個效果,那就。。。。。呵呵呵。。。。
2.2 PCL的實現
官網並未給出具體實現並測試,我不對以下代碼有效性負責。
1.超體聚類
//設定結晶參數 float voxel_resolution = 0.008f; float seed_resolution = 0.1f; float color_importance = 0.2f; float spatial_importance = 0.4f; float normal_importance = 1.0f; //生成結晶器 pcl::SupervoxelClustering<PointT> super (voxel_resolution, seed_resolution); //和點雲形式有關 if (disable_transform) super.setUseSingleCameraTransform (false); //輸入點雲及結晶參數 super.setInputCloud (cloud); super.setColorImportance (color_importance); super.setSpatialImportance (spatial_importance); super.setNormalImportance (normal_importance); //輸出結晶分割結果:結果是一個映射表 std::map <uint32_t, pcl::Supervoxel<PointT>::Ptr > supervoxel_clusters; super.extract (supervoxel_clusters);
std::multimap<uint32_t, uint32_t> supervoxel_adjacency; super.getSupervoxelAdjacency (supervoxel_adjacency);
2.LCCP
//生成LCCP分割器 pcl::LCCPSegmentation<PointT>::LCCPSegmentation LCCPseg; //輸入超體聚類結果 seg.setInputSupervoxels(supervoxel_clusters,supervoxel_adjacency); //CC效驗beta值 seg.setConcavityToleranceThreshold (concavity_tolerance_threshold); //CC效驗的k鄰點 seg.setKFactor (k_factor_arg) // seg.setSmoothnessCheck (bool_use_smoothness_check_arg,voxel_res_arg,seed_res_arg,smoothness_threshold_arg = 0.1); //SC效驗 seg.setSanityCheck (bool_use_sanity_criterion_arg); //最小分割尺寸 seg.setMinSegmentSize (min_segment_size_arg) seg.segment(); seg.relabelCloud (pcl::PointCloud<pcl::PointXYZL> &labeled_cloud_arg);
綜上所述,LCCP算法在相似物體場景分割方面有着較好的表現,對於顏色類似但棱角分明的物體可使用該算法。(比如X同學倉庫里那一堆紙箱)
3.CPC方法
CPC方法的全稱為Constrained Planar Cuts,出自論文:Constrained Planar Cuts - Object Partitioning for Point Clouds 。和LCCP方法不同,此方法的分割對象是object。此方法能夠將物體分成有意義的塊:比如人的肢體等。CPC方法可作為AI的前處理,作為RobotVision還是顯得有些不合適。但此方法不需要額外訓練,自底向上的將三維圖像分割 成有明確意義部分,是非常admirable的。
3.1 CPC方法原理
和其他基於凹凸性的方法相同,本方法也需要先進行超體聚類。在完成超體聚類之后,采用和LCCP相同的凹凸性判據獲得各個塊之間的凹凸關系。在獲得凹凸性之后,CPC方法所采取的措施是不同的。其操作稱為 半全局分割
在分割之前,首先需要生成 EEC(Euclidean edge cloud), EEC的想法比較神奇,因為凹凸性定義在相鄰兩個”片“上,換言之,定義在連接相鄰兩“片”的edge上。將每個edge抽象成一個點雲,則得到了附帶凹凸信息的點雲。如圖所示,左圖是普通點雲,但附帶了鄰接和凹凸信息。右邊是EEC,對凹邊賦權值1,其他為0。
此方法稱作 weighted RanSac
顯而易見,某處如果藍色的點多,那么就越 凹,就越應該切開(所謂切開實際上是用平面划分)。問題就轉化為利用藍點求平面了。利用點雲求一個最可能的平面當然需要請出我們的老朋友 RanSaC . 但此處引入一個評價函數,用於評價此次分割的 優良程度Sm,Pm 是EEC中的點.
單純的weighted RanSac算法並不夠。其會導致對某些圖形的錯誤分割,所以作者對此做了第一次“修補".錯誤的分割如下圖所示
此修補方法稱作 directional weighted RanSac
方法的原理很簡單,垂直於凹邊表面的點具有更高的權重,顯然,對於EEC中的凹點,只要取其少量鄰點即可估計垂直方向。
這種修補后還有一個問題,如果這個分割面過長的情況下,有可能會誤傷。如圖所示:
於是有了第二種修補方法,稱為:Locally constrained cutting
這種修補方法的原理就更加簡單粗暴了,對凹點先進行歐式分割(限制增長上限),之后再分割所得的子域里進行分割。
在修修補補之后,CPC算法終於可以投入使用了,從測試集的結果來看,效果還是很好的。
3.2 PCL的實現
在PCL中CPC類繼承自 LCCP 類,但是這個繼承我覺得不好,這兩個類之間並不存在抽象與具體的關系,只是存在某些函數相同而已。不如多設一個 凹凸分割類 作為CPC類與LCCP類的父類,所有的輸入接口等都由凹凸分割類提供。由CPC算法和LCCP算法繼承凹凸類,作為 凹凸分割 的具體實現。畢竟和 凹凸分割 有關的算法多半是對整體進行分割,和其他點雲分割算法區別較大。
//生成CPC分割器 pcl::CPCSegmentation<PointT>::CPCSegmentation seg; //輸入超體聚類結果 seg.setInputSupervoxels(supervoxel_clusters,supervoxel_adjacency); //設置分割參數 setCutting (max_cuts = 20, cutting_min_segments = 0, cutting_min_score = 0.16, locally_constrained = true, directed_cutting = true, clean_cutting = false); seg.setRANSACIterations (ransac_iterations); seg.segment(); seg.relabelCloud (pcl::PointCloud<pcl::PointXYZL> &labeled_cloud_arg);