連通域標記——實現硬幣自動計件

前言

在自動計算圖像中有幾枚硬幣的任務中,分離出前景和背景后是否就可以馬上實現自動計件,如果可以,如何實現?如果不可以,為什么?
答案是否定的。二值化之后我們的得到的只是前景總像素的多少,并不知道哪些像素屬于同一枚硬幣。想要實現自動計件功能還需要用到連通域標記的知識。
連通域標記的方法這里我們使用種子填充法:
方法

算法步驟:

1、遍歷一幅圖像。
2、如果遇到前景且該點未被標記,說明在該點附近可能存在與該點相連通的像素點,即可能存在連通域,停止遍歷。否則繼續遍歷。
3、以該點為seed點,遍歷seed點4鄰域或者8鄰域。如果同為前景,將坐標存到一個棧中,并將這點貼上label,表示已經訪問過該像素,避免重復訪問。
4、將棧中的坐標取出,以該點為seed點,重復2操作。
5、直到棧中的所有元素都取出,說明已經遍歷完了該label的所有元素。
6、label++;從一開始停止遍歷的點繼續遍歷。
7、重復2-6直到遍歷到最后一個像素

代碼實現:

*--------------------------【練習】連通域標記-------------------------------------*//*參數說明:
src_img:輸入圖像 
flag_img:作為標記的空間(在函數內部設置為單通道)
draw_img:作為輸出的圖像,不同的連通域的顏色不同
iFlag:作為判斷屬于連通域的像素目標值,一般來說我們是對二值圖進行連通域分析,所以這個值為0或者255,物體是0/1,則iFlag是0/1
type:		type==4 :用4鄰域			type==8 :用8鄰域
nums:	設定的label像素個數截斷值,被標記的連通域像素個數必須大于nums才算是正確的連通域。用來防止二值化后的效果并不好的情況。
*/
void seed_Connected_Component_labeling(Mat& src_img,Mat& flag_img,Mat& draw_img, int iFlag,int type, int nums)
{int img_row = src_img.rows;int img_col = src_img.cols;flag_img = cv::Mat::zeros(cv::Size(img_col, img_row), CV_8UC1);//標志矩陣,為0則當前像素點未訪問過draw_img = cv::Mat::zeros(cv::Size(img_col, img_row), CV_8UC3);//繪圖矩陣Point cdd[111000];                  //棧的大小可根據實際圖像大小來設置long int cddi = 0;int next_label = 1;    //連通域標簽int tflag = iFlag;long int nums_of_everylabel[100] = { 0 };	//存放每個區域的像素個數//Mat(縱坐標,橫坐標)//Point(橫坐標,縱坐標)for (int j = 0; j < img_row; j++)			//height{for (int i = 0; i < img_col; i++)		//width{//一行一行來if ((src_img).at<uchar>(j, i) == tflag && (flag_img).at<uchar>(j, i) == 0)   //滿足條件且未被訪問過{//將該像素坐標壓入棧中cdd[cddi] = Point(i, j);cddi++;//將該像素標記(flag_img).at<uchar>(j, i) = next_label;//將棧中元素取出處理while (cddi != 0){Point tmp = cdd[cddi - 1];cddi--;//對4鄰域進行標記if (type == 4){Point p[4];//鄰域像素點,這里用的四鄰域p[0] = Point(tmp.x - 1 > 0 ? tmp.x - 1 : 0, tmp.y);		//左p[1] = Point(tmp.x + 1 < img_col - 1 ? tmp.x + 1 : img_col - 1, tmp.y);//右p[2] = Point(tmp.x, tmp.y - 1 > 0 ? tmp.y - 1 : 0);//上p[3] = Point(tmp.x, tmp.y + 1 < img_row - 1 ? tmp.y + 1 : img_row - 1);//下//順時針//p[0] = Point(tmp.x, tmp.y - 1 > 0 ? tmp.y - 1 : 0);//上//p[1] = Point(tmp.x + 1 < img_col - 1 ? tmp.x + 1 : img_col - 1, tmp.y);//右//p[2] = Point(tmp.x, tmp.y + 1 < img_row - 1 ? tmp.y + 1 : img_row - 1);//下//p[3] = Point(tmp.x - 1 > 0 ? tmp.x - 1 : 0, tmp.y);		//左//逆時針//p[3] = Point(tmp.x, tmp.y - 1 > 0 ? tmp.y - 1 : 0);//上//p[2] = Point(tmp.x + 1 < img_col - 1 ? tmp.x + 1 : img_col - 1, tmp.y);//右//p[1] = Point(tmp.x, tmp.y + 1 < img_row - 1 ? tmp.y + 1 : img_row - 1);//下//p[0] = Point(tmp.x - 1 > 0 ? tmp.x - 1 : 0, tmp.y);		//左for (int m = 0; m < 4; m++){if ((src_img).at<uchar>(p[m].y, p[m].x) == tflag && (flag_img).at<uchar>(p[m].y, p[m].x) == 0) //滿足條件且未被訪問過{//將該像素坐標壓入棧中cdd[cddi] = p[m];cddi++;//將該像素標記(flag_img).at<uchar>(p[m].y, p[m].x) = next_label;}}}//對8鄰域進行標記else if (type == 8){Point p[8];//鄰域像素點,這里用的四鄰域p[0] = Point(tmp.x - 1 > 0 ? tmp.x - 1 : 0, tmp.y - 1 > 0 ? tmp.y - 1 : 0);		//左上p[1] = Point(tmp.x, tmp.y - 1 > 0 ? tmp.y - 1 : 0);//上p[2] = Point(tmp.x + 1 < img_col - 1 ? tmp.x + 1 : img_col - 1,tmp.y - 1 > 0 ? tmp.y - 1 : 0);		//右上p[3] = Point(tmp.x - 1 > 0 ? tmp.x - 1 : 0, tmp.y);		//左p[4] = Point(tmp.x + 1 < img_col - 1 ? tmp.x + 1 : img_col - 1, tmp.y);//右p[5] = Point(tmp.x - 1 > 0 ? tmp.x - 1 : 0, tmp.y + 1 < img_row - 1 ? tmp.y + 1 : img_row - 1);//左下p[6] = Point(tmp.x, tmp.y + 1 < img_row - 1 ? tmp.y + 1 : img_row - 1);//下p[7] = Point(tmp.x + 1 < img_col - 1 ? tmp.x + 1 : img_col - 1, tmp.y + 1 < img_row - 1 ? tmp.y + 1 : img_row - 1);//右下for (int m = 0; m < 7; m++){if ((src_img).at<uchar>(p[m].y, p[m].x) == tflag && (flag_img).at<uchar>(p[m].y, p[m].x) == 0) //滿足條件且未被訪問過{//將該像素坐標壓入棧中cdd[cddi] = p[m];cddi++;//將該像素標記(flag_img).at<uchar>(p[m].y, p[m].x) = next_label;}}}}next_label++;}}}next_label = next_label - 1;int all_labels = next_label;std::cout << "labels : " << next_label <<std::endl;//給不同連通域的涂色并且記錄下每個連通域的像素個數for (int j = 0;j < img_row;j++)	//行循環{for (int i = 0;i < img_col;i++)	//列循環{int now_label = (flag_img).at<uchar>(j, i);		//當前像素的labelnums_of_everylabel[now_label]++; float scale = now_label * 1.0f / all_labels;//-------【開始處理每個像素】---------------draw_img.at<Vec3b>(j, i)[0] = 255 - 255 * scale;		//B通道draw_img.at<Vec3b>(j, i)[1] = 128 - 128 * scale;		//G通道draw_img.at<Vec3b>(j, i)[2] = 255 * scale;		//R通道//-------【處理結束】---------------}}std::cout << "初步結論 : " << std::endl;for (int i = 1;i <= next_label;i++){std::cout << "labels : " << i<<"像素個數   " << nums_of_everylabel[i] <<std::endl;}std::cout << "最后結論 : " << std::endl;std::cout << "截斷像素數目 : " << nums << std::endl;for (int i = 1;i <= next_label;i++){if (nums_of_everylabel[i] <= nums){all_labels--;}}std::cout << "labels : " << all_labels << std::endl;}int main()
{Mat flag_img;Mat draw_img;Mat srcImage = imread("D:\\opencv_picture_test\\閾值處理\\硬幣.png", 0);	//讀入的時候轉化為灰度圖//Mat srcImage = imread("D:\\opencv_picture_test\\閾值處理\\黑白.jpg", 0);	//讀入的時候轉化為灰度圖namedWindow("原始圖", WINDOW_NORMAL);//WINDOW_NORMAL允許用戶自由伸縮窗口imshow("原始圖", srcImage);cout << "srcImage.rows : " << srcImage.rows << endl;		//308cout << "srcImage.cols : " << srcImage.cols << endl;		//372Mat dstImage;dstImage.create(srcImage.rows, srcImage.cols, CV_8UC1);//閾值處理+二值化My_artificial(&srcImage, &dstImage, 84);//	flag_img = cv::Mat::zeros(src.size(), src.type());//cvtColor(src, src, COLOR_RGB2GRAY);    //這一句很重要,必須保證輸入的是單通道的圖,否則所讀取的數據是錯誤的double time0 = static_cast<double>(getTickCount());	//記錄起始時間seed_Connected_Component_labeling(dstImage,flag_img,draw_img,255,4,500);		//白色部分被標記time0 = ((double)getTickCount() - time0) / getTickFrequency();cout << "此方法運行時間為:" << time0 << "秒" << endl;	//輸出運行時間imshow("dstImage", dstImage);namedWindow("draw_img", WINDOW_NORMAL);//WINDOW_NORMAL允許用戶自由伸縮窗口imshow("draw_img", draw_img);waitKey(0);return 0;
}

實現效果:

原圖:
1
二值圖(可以看到有幾個噪點,而且圖像的右邊和上邊是白色的,這是因為原圖我是截圖的,邊界并沒有剪裁好,這點在下面的連通域標記會有影響)
2
我給屬于不同連通域的物體涂上不同的顏色。
3
下面是打印出來的信息:初步得到的label是19個,其中label1就是我所說的截圖邊界問題。其他的幾個像素個數小的就是噪點。
通過設定門限,像素個數小于500的標簽物體我們將它視為噪聲。最后得到的label數目正好是10,也就是硬幣的數目。
4

發現的問題

連通域標記函數代碼部分,可以看到我還嘗試了其他兩種遍歷seed周圍元素的方式,分別是順時針和逆時針。但是運算速度沒有第一種快,至于原因我沒有深究。希望有心人能給我講解一波。此外,試了一下8鄰域,運算速度也得到了下降。
代碼
這就是我說的剪裁錯誤,嘿嘿。剪裁錯誤
此外,二值化的方法我是用的人工調整,原圖受到非均勻光線的照射,全局大津閾值得到的效果并不是很好,反而由于直方圖雙峰性比較明顯,迭代法看起來還不錯。不過為了連通域標記的時候能夠準確一點,我就用滑條調整閾值了。
滑動條調整閾值的代碼在這兒:https://blog.csdn.net/qq_42604176/article/details/104764731
迭代法、大津的代碼在這兒:https://blog.csdn.net/qq_42604176/article/details/104341126

3.15更新,加入形態學腐蝕操作

首先回顧之前遇到的問題:受到噪聲影響,十個硬幣竟然貼了19個labels,盡管利用限制像素個數的方法來限制,但這種方法有許多弊端。
這幾天學習了一些簡單的形態學操作,其中腐蝕操作有個作用:去除黏連像素以及噪聲
這不就正好能解決之前遇到的問題嘛!
操作也很簡單,加上兩行代碼就行。
代碼
結果運行如下(把自己簡陋的限制像素函數去掉了)
效果
效果很好啊!
效果
關于腐蝕的詳細講解請看這邊:
https://blog.csdn.net/qq_42604176/article/details/104815801

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/378742.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/378742.shtml
英文地址,請注明出處:http://en.pswp.cn/news/378742.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Storm資料匯總

一、Storm集群安裝部署 網上關于storm集群部署都大同小異。 Storm下載地址&#xff1a;http://storm-project.net Storm項目地址&#xff1a;https://github.com/nathanmarz/storm 目前的版本不支持ZooKeeper3.4.5版本&#xff0c;而支持ZooKeeper3.3.3版本。 我當時沒注意這…

getlong_Java即時類| 帶示例的getLong()方法

getlong即時類getLong()方法 (Instant Class getLong() method) getLong() method is available in java.time package. getLong()方法在java.time包中可用。 getLong() method is used to get the value as long for the given temporal field from this Instant. getLong()方…

python作品闡述_Python網絡編程基礎的作品鑒賞-

Python網絡編程基礎的作品鑒賞Python網絡編佳入門圖書&#xff01;175個完整&#xff0c;實踐出真知&#xff0c;SocketsDNS、Web Service、FTP、Email、SMTP、POP、IMAP、SocketServe、CGI、XML、Forking、數據庫客戶端、多線程、異步通信……完整涵蓋網絡編程的方方面面“這可…

09-梯度運算

梯度運算膨脹操作-腐蝕操作&#xff0c;這里的-操作是圖像的減法&#xff0c;可不是簡單的加減乘除運算 為了更加形象生動&#xff0c;先將膨脹和腐蝕操作的結果進行合并展示&#xff0c;然后再與梯度運算進行比較 cv2.morphologyEx(pie,cv2.MORPH_GRADIENT,kernel) 第一個參數…

JavaScript學習筆記-我的淺顯認識

一&#xff1a;什么是javascript? 它是一種基于對象和事件驅動的解釋性的安全的與平臺無關的免費的瀏覽器腳本語言。 二&#xff1a;有什么用途&#xff1f; 使表單的驗證放在客戶端&#xff0c;更快捷反應&#xff0c;增強網頁的交互性。設計一些特效&#xff0c;如菜單&…

典型瀑布模型四個階段_古典瀑布模型的不同階段

典型瀑布模型四個階段The different phases that are included in the classical waterfall model are: 經典瀑布模型包含的不同階段是&#xff1a; Feasibility study 可行性研究 Requirement analysis and specification 需求分析和規范 Design 設計 Coding and unit testin…

Unity-Shader-渲染隊列

Unity-Shader-渲染隊列 渲染簡介Unity中的幾種渲染隊列Background (1000)最早被渲染的物體的隊列。Geometry (2000) 不透明物體的渲染隊列。大多數物體都應該使用該隊列進行渲染&#xff0c;也就是Unity Shader中默認的渲染隊列。AlphaTest (2450) 有透明通道&#xff0c;需要進…

MyBatis ResultMap(2)

SQL 映射XML 文件是所有sql語句放置的地方。需要定義一個workspace&#xff0c;一般定義為對應的接口類的路徑。寫好SQL語句映射文件后&#xff0c;需要在MyBAtis配置文件mappers標簽中引用&#xff0c;例如&#xff1a; Xml代碼 <mappers> <mapper resource&qu…

形態學操作——腐蝕與膨脹

預備知識 結構元&#xff08;SE&#xff09; 1、結構元的中心一般來說是放在其重心位置處&#xff0c;但原則上原點的選擇是依賴于你要解決的問題的。 2、對圖像操作時&#xff0c;我們要求結構元是矩形陣列。&#xff08;在結構元的基礎上添加較少的背景元素實現&#xff09…

java集合轉換_java各種集合的轉換

內容&#xff1a;1、List轉Array 2、Array轉List3、String轉int[],String[](對單個字符) 4、數組、List、Set、Map相互轉換5、一行輸入多個元素方法常用集合&#xff1a;Map、Set、List、Array、String1、List轉Array&#xff…

10-禮帽與黑帽操作

cv2.morphologyEx(img,cv2.MORPH_TOPHAT,kernel) 第一個參數&#xff1a;圖像對象名稱 第二個參數&#xff1a;運算類型TOPHAT為禮帽運算 第三個參數&#xff1a;卷積核的大小 禮帽運算&#xff1a;原始的輸入-開運算&#xff08;先腐蝕再膨脹&#xff09; 原始帶刺兒&#xff…

2011 cnblogs開通--給力吧

在信息傳遞如此迅速的時代&#xff0c;自己慢慢的老去&#xff0c;是否要想寫些什么&#xff0c;跟大家一起分享學習、工作、生活的酸甜苦辣&#xff0c;提高自己的各方面的能力;往后將慢慢記錄自己的程序人生吧&#xff0c;就什么多了&#xff0c;作為自己cnblogs第一篇吧^_^轉…

Android----獲取activity上所有的控件

01/**02 * note 獲取該activity所有view03 * author liuh04 * */05 public List<View> getAllChildViews() {06 View view this.getWindow().getDecorView();07 return getAllChildViews(view);08 }09 10 private List<View> …

微信小程序 查找兄弟節點_使用C ++程序在鏈接列表中查找節點

微信小程序 查找兄弟節點Given a linked list and an integer N, you need to find and return index where N is present in the Linked List. Return -1 if n is not present in the Linked List. 給定一個鏈表和一個整數N&#xff0c;您需要查找并返回索引&#xff0c;其中鏈…

形態學操作——開閉運算、頂帽底(黑)帽變換

膨脹和腐蝕運算的問題&#xff1a; 邊緣形狀發生了變化&#xff0c;膨脹發生了擴張&#xff0c;腐蝕發生了收縮 目標物體變形&#xff0c;對識別時的特征提取會造成影響 解決方法&#xff1a; 開操作: B對A的開操作就是先B對A腐蝕&#xff0c;緊接著用B對結果進行膨脹 先腐…

java 基礎實戰_Java基礎實戰(三)

是否是否是否是否獲取字符串字符數組大寫?小寫?數字?非字母與數字大寫字母小寫字母數字i結束ii1第一步 拆分字符串為字符數組&#xff1a;static void count(String str) {// 將字符串拆分為字符數組char[] charArray str.toCharArray();}第二步 定義相關變量記錄結果&…

11-圖像梯度-Sobel算子

圖像梯度是指圖像某像素在x和y兩個方向上的變化率&#xff08;與相鄰像素比較&#xff09;&#xff0c;是一個二維向量&#xff0c;由2個分量組成&#xff0c;X軸的變化、Y軸的變化 。 其中X軸的變化是指當前像素右側&#xff08;X加1&#xff09;的像素值減去當前像素左側&…

給IE有效指定編碼

<title>下一站</title> <meta http-equiv"Content-Type" content"text/html; charsetutf-8" /> IE每次打開&#xff0c;均是一片空白&#xff0c;查看右鍵&#xff0d;編碼&#xff0c;顯示是GB2312。要手功改為UTF-8后才能正常顯示頁面…

形態學操作——擊中擊不中變換

操作目的 HitMiss變換是形態檢測的一個工具&#xff0c;通過定義形狀模板可以在圖像中獲取同一形狀物體的位置坐標。 算法講解 1、用擊中結構去腐蝕原始圖像得到擊中結果X&#xff08;這個過程可以理解為在原始圖像中尋找和擊中結構完全匹配的模塊&#xff0c;匹配上了之后&…

stack.pop()方法_C.示例中的Stack.Pop()方法

stack.pop()方法C&#xff03;Stack.Pop()方法 (C# Stack.Pop() method) Stack.Pop() method is used to remove an object from the top of the stack. The method removes and returns the object from the top. Stack.Pop()方法用于從堆棧頂部刪除對象。 該方法從頂部刪除并…