前言
??工作中遇到需要通過OpenCV找到圖片主體體積占圖片百分比的比例,這里做一個問題解決思路的記錄遍坟。該方面新手小白拳亿,有不對的地方可以評論指出哈 。
重要API
Sobel算法
索貝爾算子是計算機視覺領(lǐng)域的一種重要處理方法愿伴。
主要用于獲得數(shù)字圖像的一階梯度肺魁,常見的應(yīng)用和物理意義是邊緣檢測。
索貝爾算子是把圖像中每個像素的上下左右四領(lǐng)域的灰度值加權(quán)差隔节,在邊緣處達到極值從而檢測邊緣鹅经。在技術(shù)上,它是一離散性差分算子怎诫,用來運算圖像亮度函數(shù)的梯度之近似值瘾晃。在圖像的任何一點使用此算子,將會產(chǎn)生對應(yīng)的梯度矢量或是其法矢量幻妓。索貝爾算子不但產(chǎn)生較好的檢測效果蹦误,而且對噪聲具有平滑抑制作用,但是得到的邊緣較粗肉津,且可能出現(xiàn)偽邊緣强胰。
該算子包含兩組3x3的矩陣,分別為橫向及縱向妹沙,將之與圖像作平面卷積偶洋,即可分別得出橫向及縱向的亮度差分近似值。如果以A代表原始圖像距糖,Gx及Gy分別代表經(jīng)橫向及縱向邊緣檢測的圖像玄窝,其公式如下:
圖像的每一個像素的橫向及縱向梯度近似值可用以下的公式結(jié)合,來計算梯度的大小肾筐。
然后可用以下公式計算梯度方向哆料。
在以上例子中,如果以上的角度θ等于零吗铐,即代表圖像該處擁有縱向邊緣,左方較右方暗 【引用:百度百科】
//src : 輸入圖像 dst: 輸出圖像
//ddepth: 輸出圖像的深度(可以理解為數(shù)據(jù)類型)杏节,-1表示與原圖像相同的深度
//dx: dy: 當組合為dx=1,dy=0時求x方向的一階導(dǎo)數(shù)唬渗,當組合為dx=0,dy=1時求y方向的一階導(dǎo)數(shù)
//ksize: (可選參數(shù))Sobel算子的大小典阵,必須是1,3,5或者7,默認為3。
//scale: 對導(dǎo)數(shù)計算結(jié)果進行縮放的縮放因子镊逝,默認系數(shù)為1壮啊,不進行縮放
//delta: 偏值,在計算結(jié)果中加上偏值撑蒜。
//borderType: 像素外推法選擇標志歹啼。默認參數(shù)為BORDER_DEFAULT,表示不包含邊界值倒序填充座菠。
public static void Sobel(Mat src, Mat dst, int ddepth, int dx, int dy, int ksize, double scale, double delta, int borderType)
- 注意點:
圖像深度是指存儲每個像素值所用的位數(shù)狸眼,例如cv2.CV_8U,指的是8位無符號數(shù)浴滴,取值范圍為0~255拓萌,超出范圍則會被截斷(截斷指的是,當數(shù)值大于255保留為255升略,當數(shù)值小于0保留為0微王,其余不變)。
??具體還有:CV_16S(16位無符號數(shù))品嚣,CV_16U(16位有符號數(shù))炕倘,CV_32F(32位浮點數(shù)),CV_64F(64位浮點數(shù))
??當ddepth 輸出圖像深度采用CV_8U翰撑,由于Sobel算子在計算X方向梯度時激才,如果某像素點右側(cè)像素值大于左側(cè)像素值,則梯度大小為正保留额嘿,相反梯度大小為負被截斷瘸恼,梯度大小保存為0。這里可以使用 CV_32F 防止數(shù)據(jù)存在負數(shù)情況册养。在我們使用CV_32F數(shù)據(jù)類型求出XY階后东帅,需要配合使用函數(shù)convertScaleAbs()將圖像深度為CV_64F的梯度圖像重新轉(zhuǎn)化為CV_8U,這是由于函數(shù)cv2.imshow()的默認顯示為8位無符號數(shù),即[0,255]球拦。
具體思路
??第一步靠闭,使用OpenCV的 Sobel算子來計算x,y方向上的梯度坎炼,在x方向上減去y方向上的梯度愧膀,通過這個減法,我們會留下有高水平梯度和低垂直梯度的圖像區(qū)域谣光。
Mat gradX = new Mat();
Mat gradY = new Mat();
Mat binary = new Mat();
//求X Y 階 轉(zhuǎn)換為32位浮點數(shù)個數(shù)
Imgproc.Sobel(grayImage, gradX, CvType.CV_32F, 1, 0, -1);
Imgproc.Sobel(grayImage, gradY, CvType.CV_32F, 0, 1, -1);
//相減
Core.subtract(gradX, gradY, binary);
//轉(zhuǎn)換數(shù)據(jù)格式CV_8U
Core.convertScaleAbs(binary, binary);
以上步驟我們可以得到一個帶有很多噪點的邊界圖檩淋,為了剔除掉那些噪點對于主體的判斷,我們需要使用 blur 方法 和黑白兩極化對圖片進行處理
??第二步萄金,使用低通濾潑器平滑圖像(9 x 9內(nèi)核),這將有助于平滑圖像中的高頻噪聲蟀悦。低通濾波器的目標是降低圖像的變化率媚朦。如將每個像素替換為該像素周圍像素的均值。這樣就可以平滑并替代那些強度變化明顯的區(qū)域日戈。
//去噪
Imgproc.blur(binary, binary, new Size(9.0, 9.0));
//對模糊圖像二值化询张。梯度圖像中不大于90的任何像素都設(shè)置為0(黑色)。 否則浙炼,像素設(shè)置為255(白色)為第五步做準備工作
Imgproc.threshold(binary, binary, 90.0, 255.0, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
??第三步份氧,通過上述方法可以得到一個黑白圖,但是圖片中可能會存在很多空白空隙弯屈, 我們要用白色填充這些空余蜗帜,使得后面的程序更容易識別昆蟲區(qū)域,這需要做一些形態(tài)學(xué)方面的操作季俩。
//為形態(tài)學(xué)操作返回指定大小和形狀的結(jié)構(gòu)元素 用于形態(tài)學(xué)處理
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(25.0, 25.0));
// 使用腐蝕和膨脹作為基本操作來執(zhí)行高級形態(tài)學(xué)轉(zhuǎn)換
Imgproc.morphologyEx(binary,binary,Imgproc.MORPH_CLOSE,kernel);
??第四步钮糖, 通過上述處理,可能還會存在一些 大的噪點酌住, 可以通過形態(tài)學(xué)腐化和膨脹進行消除
Point p = new Point(-1.0, -1.0);
Imgproc.erode(binary, binary, ker, p, 4);
Imgproc.dilate(binary, binary, ker, p, 4);
??第五步店归,通過 findContours()函數(shù)框選出主體的位置信息
List<MatOfPoint> contours = new ArrayList<>();
Mat hierarchy = new Mat();
// binary : 要檢索的圖片,必須是為二值圖酪我,即黑白的
//hierarchy : 結(jié)果返回值
Imgproc.findContours(binary,contours,hierarchy,
Imgproc.RETR_EXTERNAL,Imgproc.CHAIN_APPROX_SIMPLE);
findContours 函數(shù) 最后兩個參數(shù)消痛,mode 和 method 單獨拿出來列舉下 不同的參數(shù)代表的含義
mode :
Imgproc.RETR_EXTERNAL 表示只檢測外輪廓
Imgproc.RETR_LIST 檢測的輪廓不建立等級關(guān)系
Imgproc.RETR_CCOMP 建立兩個等級的輪廓,上面的一層為外邊界都哭,里面的一層為內(nèi)孔的邊界信息秩伞。如果內(nèi)孔內(nèi)還有一個連通物體,這個物體的邊界也在頂層欺矫。
Imgproc.RETR_TREE 建立一個等級樹結(jié)構(gòu)的輪廓纱新。
method :
Imgproc.CHAIN_APPROX_NONE 存儲所有的輪廓點,相鄰的兩個點的像素位置差不超過1穆趴,即max(abs(x1-x2)脸爱,abs(y2-y1))==1
Imgproc.CHAIN_APPROX_SIMPLE 壓縮水平方向,垂直方向未妹,對角線方向的元素簿废,只保留該方向的終點坐標,例如一個矩形輪廓只需4個點來保存輪廓信息
??結(jié)果都存儲在 contours 數(shù)組中络它,我們可以使用他進行一些業(yè)務(wù)判斷族檬。
??由于脫敏原因,這里沒有demo圖片化戳,該篇文章 主要參考文章對我的幫助非常大单料,小白這里只是做一個開發(fā)記錄,今天的這篇文章就到這里。