圖像形態學
- 一、像素的距離
- 二、像素的鄰域
- 三、膨脹與腐蝕
- 3.1、結構元素
- 3.2、腐蝕
- 3.3、膨脹
- 四、形態學操作
- 4.1、開運算和閉運算
- 4.2、頂帽和黑帽
- 4.3、形態學梯度
- 4.4、擊中擊不中
一、像素的距離
圖像中像素之間的距離有多種度量方式,其中常用的有歐式距離、棋盤距離和街區距離。
1. 歐式距離:
歐式距離是指連接兩個點的直線距離。歐式距離用數學公式可以表達如下:
//勾股定理計算
d^2 = (x1-x2)^2+(y1-y2)^2
2. 棋盤距離:
棋盤距離也叫切比雪夫距離,棋盤距離可以用國際象棋中王的走法來說明,也就是王從一個位置走到另一個位置最少需要多少步。
例如,王從b3走到g6最少需要幾步?即b3->c4->d5->e6->f6->g6,共5步。不難發現,起始位置和終止位置在棋盤上的距離是,橫向5格,縱向3格,所以棋盤距離實際上是橫向距離與縱向距離之間的較大者,用數學公式表達如下:
//兩個向量在任意坐標維度上的最大差值
d = max(|x1-x2|, |y1-y2|)
3. 街區距離:
街區距離也稱為曼哈度距離。與棋盤距離不同,在街區距離中只允許橫向或縱向移動,不允許斜向移動。那么,上例中王的兩個位置即為8步。用數據公式表達如下:
d = |x1-x2| + |y1-y2|
//計算一張圖像中非零像素到最近的零像素的距離,即到零像素的最短距離
void Imgproc.distanceTransform(Mat src, Mat dst, int dstanceType, int maskSize)
- src:輸入圖像,必須為8位單通道
- dst:含計算距離的輸出圖像,圖像深度為CV_8U或CV_32F的單通道圖像,尺寸與輸入圖像相同。
- distanceType:距離類型,可選參數如下:
- Imgproc.DIST_USER:用戶自定義距離
- Imgproc.DIST_L1:街區距離
- Imgproc.DIST_L2:歐式距離
- Imgproc.DIST_C:棋盤距離
- Imgproc.DIST_L12:d=2(sqrt(1+x*x/2)-1)
- Imgproc.DIST_FAIR:d=c^2(|x|/c-log(1+|x|/c)),其中c=1.3998
- Imgproc.DIST_WELSCH:d=c^2/2(1-exp(-(x/c)^2)),其中c=2.9846
- Imgproc.DIST_HUBER:d=|x|<c ? x^2/2 : c(|x|-c/2),其中c=1.345
- maskSize:距離變化掩碼矩陣尺寸,可選參數如下
- Imgproc.DIST_MASK_3:數字值=3
- Imgproc.DIST_MASK_5:數字值=5
- Imgproc.DIST_MASK_PRECISE
為了使用加速算法,掩碼矩陣必須是對稱的。在計算歐氏距離時,掩碼矩陣為3時只是粗略計算像素距離(水平和垂直方向的變化量為1,對角線方向的變化量為1.4)。
public class DistanceTransform {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//構建一個5*5的小矩陣并導入數據Mat src = new Mat(5, 5, CvType.CV_8UC1);byte[] data = new byte[] {1, 1, 1, 1, 1,1, 1, 1, 1, 1,1, 1, 0, 1, 1,1, 1, 1, 1, 1,1, 1,1 ,1 , 1};src.put(0, 0, data);//打印矩陣數據System.out.println("輸入矩陣:");System.out.println(src.dump());System.out.println();//計算棋盤距離并輸出Mat dst = new Mat();Imgproc.distanceTransform(src, dst, Imgproc.DIST_C, 3);System.out.println("棋盤距離:");System.out.println(dst.dump());System.out.println();//計算街區距離并輸出Imgproc.distanceTransform(src, dst, Imgproc.DIST_L1, 3);System.out.println("街區距離:");System.out.println(dst.dump());System.out.println();//計算歐氏距離并輸出Imgproc.distanceTransform(src, dst, Imgproc.DIST_L2, 3);System.out.println("歐氏距離:");System.out.println(dst.dump());System.out.println();}
}
輸入矩陣:
[ 1, 1, 1, 1, 1;1, 1, 1, 1, 1;1, 1, 0, 1, 1;1, 1, 1, 1, 1;1, 1, 1, 1, 1]棋盤距離:
[2, 2, 2, 2, 2;2, 1, 1, 1, 2;2, 1, 0, 1, 2;2, 1, 1, 1, 2;2, 2, 2, 2, 2]街區距離:
[4, 3, 2, 3, 4;3, 2, 1, 2, 3;2, 1, 0, 1, 2;3, 2, 1, 2, 3;4, 3, 2, 3, 4]歐氏距離:
[2.7385864, 2.324295, 1.9100037, 2.324295, 2.7385864;2.324295, 1.3692932, 0.95500183, 1.3692932, 2.324295;1.9100037, 0.95500183, 0, 0.95500183, 1.9100037;2.324295, 1.3692932, 0.95500183, 1.3692932, 2.324295;2.7385864, 2.324295, 1.9100037, 2.324295, 2.7385864]
上面的程序僅用于說明像素距離的計算方法。利用計算出的距離可以實現很多功能。下面示例說明如何用像素距離實現輪廓的細化:
public class Thinning {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//讀取圖像為灰度圖Mat image = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/wang.png");Mat gray = new Mat();Imgproc.cvtColor(image, gray, Imgproc.COLOR_BGR2GRAY);//將灰度圖反相并顯示Core.bitwise_not(gray, gray);HighGui.imshow("gray", gray);HighGui.waitKey(0);//進行高斯濾波和二值化處理Imgproc.GaussianBlur(gray, gray, new Size(5, 5), 2);Imgproc.threshold(gray, gray, 20, 255, Imgproc.THRESH_BINARY);HighGui.imshow("Binary", gray);HighGui.waitKey(0);//計算街區距離Mat thin = new Mat(gray.size(), CvType.CV_32FC1);Imgproc.distanceTransform(gray, thin, Imgproc.DIST_L1, 3);//獲取最大的街區距離float max = 0;for (int i = 0; i < thin.rows(); i++) {for (int j = 0; j < thin.cols(); j++) {float[] f = new float[3];thin.get(i, j, f);if (f[0] > max) {max = f[0];}}}//定義用于顯示結果的矩陣,背景為全黑Mat show = Mat.zeros(gray.size(), CvType.CV_8UC1);//將距離符合一定條件的像素設為白色for (int i = 0; i < thin.rows(); i++) {for (int j = 0; j < thin.cols(); j++) {float[] f = new float[3];thin.get(i, j, f);if (f[0] > max / 3) {show.put(i, j, 255);}}}//在屏幕上顯示最后結果HighGui.imshow("thin", show);HighGui.waitKey(0);System.exit(0);}
}
原圖灰度圖反相:
二值化:
通過計算街區距離,細化為原來的1/3:
二、像素的鄰域
像素的鄰域是指與某一像素相鄰的像素集合。鄰域通常分為4鄰域、8鄰域和D鄰域,如下圖:
//4鄰域,當前像素為中心,上、下、左、右4個像素11 P 11//8鄰域,當前像素為中心,上、下、左、右、左上、左下、右上、右下8個像素
1 1 1
1 P 1
1 1 1//D鄰域,當前像素為中心,左上、左下、右上、右下4個像素
1 1P
1 1
8鄰域=4鄰域+D鄰域,OpenCV中有如下函數標記圖像中的連通域:
int Imgproc.connectedComponents(Mat image, Mat labels, int connectivity, int ltype)
- image:需要標記的8位單通道圖像
- labels:標記不同連通域后的輸出圖像
- connectivity:標記連通域時的鄰域種類,8代表8鄰域,4代表4鄰域
- ltype:輸出圖像的標簽類型,目前支持CV_32S和CV_16U
- int返回值:連通域個數
public class ConnectedComponents {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//讀取圖像并顯示Mat src = Imgcodecs.imread("F:/IDEAworkspace/opencv/src/main/java/demo2/seed.png");HighGui.imshow("src", src);HighGui.waitKey(0);//將圖像轉位灰度圖并二值化Mat gray = new Mat();Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);Core.bitwise_not(gray, gray);//反相操作Mat binary = new Mat();Imgproc.threshold(gray, binary, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);//在屏幕顯示二值圖HighGui.imshow("Binary", binary);HighGui.waitKey(0);//標記連通域Mat labels = new Mat(src.size(), CvType.CV_32S);//num為連通域個數int num = Imgproc.connectedComponents(binary, labels, 8, CvType.CV_32S);//定義顏色數組,用于不同的連通域Scalar[] colors = new Scalar[num];//隨機生成顏色Random rd = new Random();for (int i = 0; i < num; i++) {int r = rd.nextInt(256);int g = rd.nextInt(256);int b = rd.nextInt(256);colors[i] = new Scalar(r, g, b);}//標記各連通域,dst為用于標記的圖像Mat dst = new Mat(src.size(), src.type(), new Scalar(255, 255, 255));int width = src.cols();int height = src.rows();for (int i = 0; i < height; i++) {for (int j = 0; j < width; j++) {//獲取標簽號int label = (int)labels.get(i, j)[0];//黑色背景色不變if (label == 0) {continue;}//根據標簽號設置顏色double[] val = new double[3];val[0] = colors[label].val[0];val[1] = colors[label].val[1];val[2] = colors[label].val[2];dst.put(i, j, val);}}//在屏幕上顯示最后結果HighGui.imshow("labelled", dst);HighGui.waitKey(0);System.exit(0);}
}
原圖:
二值圖:
用不同顏色標記后的連通域:
//標記圖像中的連通域,并輸出統計信息
int Imgproc.connectedComponentsWithStats(Mat image, Mat labels, Mat stats, Mat centroids)
- image:需要標記的8位單通道圖像
- labels:標記不同連通域后的輸出圖像
- stats:每個標簽的統計信息輸出,含背景標簽,數據類型為CV_32S
- centroids:每個連通域的質心坐標,數據類型為CV_64F
public class ConnectedComponentsWithStats {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//讀取圖像并顯示Mat src = Imgcodecs.imread("F:/IDEAworkspace/opencv/src/main/java/demo2/seed.png");HighGui.imshow("src", src);HighGui.waitKey(0);//將圖像轉位灰度圖并二值化Mat gray = new Mat();Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);Core.bitwise_not(gray, gray);//反相操作Mat binary = new Mat();Imgproc.threshold(gray, binary, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);//在屏幕顯示二值圖HighGui.imshow("Binary", binary);HighGui.waitKey(0);//標記連通域Mat labels = new Mat(src.size(), CvType.CV_32S);Mat stats = new Mat();Mat centroids = new Mat();int num = Imgproc.connectedComponentsWithStats(binary, labels, stats, centroids);//定義顏色數組,用于不同的連通域Scalar[] colors = new Scalar[num];//隨機生成顏色Random rd = new Random();for (int i = 0; i < num; i++) {int r = rd.nextInt(256);int g = rd.nextInt(256);int b = rd.nextInt(256);colors[i] = new Scalar(r, g, b);}//標記各連通域,dst為用于標記的圖像Mat dst = new Mat(src.size(), src.type(), new Scalar(255, 255, 255));int width = src.cols();int height = src.rows();for (int i = 0; i < height; i++) {for (int j = 0; j < width; j++) {//獲取標簽號int label = (int)labels.get(i, j)[0];//將背景以外的連通域設為黑色if (label != 0) {double[] val = new double[]{0, 0, 0};dst.put(i, j, val);}}}//繪制各連通域的質心和外接矩形for (int i = 1; i < num; i++) {//獲取連通域中心位置double cx = centroids.get(i, 0)[0];double cy = centroids.get(i, 1)[0];int left = (int)stats.get(i, Imgproc.CC_STAT_LEFT)[0];int top = (int)stats.get(i, Imgproc.CC_STAT_TOP)[0];width = (int)stats.get(i, Imgproc.CC_STAT_WIDTH)[0];height = (int)stats.get(i, Imgproc.CC_STAT_HEIGHT)[0];//繪制連通域質心Imgproc.circle(dst, new Point(cx, cy), 2, new Scalar(0, 0, 255), 2, 8, 0);//繪制連通域外接矩形Imgproc.rectangle(dst, new Point(left, top), new Point(left + width, top + height), colors[i], 2, 8, 0);}//在屏幕上顯示最后結果HighGui.imshow("labelled", dst);HighGui.waitKey(0);System.exit(0);}
}
原圖:
二值化:
標記質心和外接矩形的連通域圖:
三、膨脹與腐蝕
3.1、結構元素
腐蝕和膨脹是形態學中最基本的操作,其他的形態學操作,如開運算、閉運算、頂帽、黑帽等,本質上都是腐蝕和膨脹的組合運算。形態學一般需要兩個輸入參數,一個是用于操作的圖像,另一個是類似卷積核的元素,稱為結構元素。結構元素還有一個用于定位的參考點,稱為錨點。
//根據指定的尺寸和形狀生成形態學操作的結構元素
Mat Imgproc.getStructuringElement(int shape, Size ksize, Point anchor)
- shape:結構元素的形狀,有下列選項:
- Imgproc.MORPH_RECT:矩形結構元素
- Imgproc.MORPH_CROSS:十字型結構元素
- Imgproc.MORPH_ELLIPSE:橢圓結構元素,矩形的內接橢圓
- ksize:結構元素的尺寸
- anchor:結構元素內的錨點,默認值(-1, -1),表示錨點位于結構元素中心。只有十字型的結構元素的形狀取決于錨點的位置。其他情況僅僅用于調節形態學操作結果的平移量。
public class StructureElement {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//矩形結構元素Mat k0 = Imgproc.getStructuringElement(0, new Size(3, 3));System.out.println(k0.dump());System.out.println();//十字型結構元素Mat k1 = Imgproc.getStructuringElement(1, new Size(3, 3));System.out.println(k1.dump());System.out.println();//橢圓結構元素Mat k2 = Imgproc.getStructuringElement(2, new Size(7, 7));System.out.println(k2.dump());System.out.println();}
}
[ 1, 1, 1;1, 1, 1;1, 1, 1][ 0, 1, 0;1, 1, 1;0, 1, 0][ 0, 0, 0, 1, 0, 0, 0;0, 1, 1, 1, 1, 1, 0;1, 1, 1, 1, 1, 1, 1;1, 1, 1, 1, 1, 1, 1;1, 1, 1, 1, 1, 1, 1;0, 1, 1, 1, 1, 1, 0;0, 0, 0, 1, 0, 0, 0]
3.2、腐蝕
腐蝕是求局部最小值的操作。經過腐蝕操作后,圖像中的高亮區域會縮小,就像被腐蝕了一樣。
腐蝕運算的原理如下圖,原圖像標有1的像素為高亮區域,結構元素中心的像素為錨點。腐蝕操作時用結構元素掃描原圖像,用結構元素與覆蓋區域的像素進行與運算,如果所有像素的運算結果都是1,則該像素值為1,否則為0。
//原圖像
0 0 0 0 0 0
0 0 1 1 1 0
0 1 1 1 1 0
0 1 1 1 1 0
0 1 1 0 0 0
0 0 1 1 0 0//結構元素
0 1 0
1 1 1
0 1 0//腐蝕運算結果
0 0 0 0 0 0
0 0 0 0 0 0
0 0 1 1 0 0
0 0 1 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
可以發現,原圖像中高亮區域有15像素,經過腐蝕操作后只有3像素了,所以的結構相當于給高亮區域瘦身,瘦身的效果取決于結構元素,而結構元素可以根據需求自行定義。需要注意的是,腐蝕操作及膨脹操作等形態學操作都是針對高亮區域。如果原圖像是黑白二值圖像,則被腐蝕的是白色區域,如果希望黑色區域被腐蝕,則可以在操作前先進行反向操作。
//用特定的結構元素對圖像進行腐蝕操作
void Imgproc.erode(Mat src, Mat dst, Mat kernel, Point anchor, int iterations)
- src:輸入圖像,通道數任意,但深度應為CV_8U、CV_16U、CV_16S、CV_32F或CV_64F
- dst:輸出圖像,和src具有相同的尺寸和數據類型
- kernel:用于腐蝕操作的結構元素,可以用getStructuringElement()函數生成
- anchor:結構元素內錨點的位置,默認(-1, -1),表示錨點位于結構元素中心
- iterations:腐蝕的次數
此方法可以設置迭代次數,即一次完成多次腐蝕操作。如果只需腐蝕一次,則可將后兩個參數省略。
public class Erode {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//讀取圖像并顯示Mat src = Imgcodecs.imread("F:/IDEAworkspace/opencv/src/main/java/demo/wang.png");HighGui.imshow("src", src);HighGui.waitKey(0);//將圖像反向并顯示Core.bitwise_not(src, src);HighGui.imshow("negative", src);HighGui.waitKey(0);//生成十字型結構元素Mat kernel = Imgproc.getStructuringElement(1, new Size(3, 3));Point anchor = new Point(-1, -1);//腐蝕操作1次并顯示Mat dst = new Mat();Imgproc.erode(src, dst, new Mat());HighGui.imshow("erode=1", dst);HighGui.waitKey(0);//腐蝕操作3次并顯示Imgproc.erode(src, dst, kernel, anchor, 3);HighGui.imshow("erode=3", dst);HighGui.waitKey(0);System.exit(0);}
}
原圖:
反向:
一次腐蝕:
三次腐蝕:
上面的示例只能看到腐蝕的結構,無法對腐蝕的細節進行研究。下面用一個完整的程序說明:
public class Erode2 {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//構建一個6*6的小矩陣并導入數據Mat src = new Mat(6, 6, CvType.CV_8UC1);byte[] data = new byte[] {0,0,0,0,0,0,0,0,1,1,1,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1,1,0,0,0,0,0,1,1,0,0};src.put(0, 0, data);//打印矩陣System.out.println(src.dump());System.out.println();//構建十字型結構元素Mat kernel = Imgproc.getStructuringElement(1, new Size(3, 3));Mat dst = new Mat(6, 6, CvType.CV_8UC1);//進行腐蝕操作并輸出腐蝕后的矩陣Imgproc.erode(src, dst, kernel);System.out.println(dst.dump());}
}
[ 0, 0, 0, 0, 0, 0;0, 0, 1, 1, 1, 0;0, 1, 1, 1, 1, 0;0, 1, 1, 1, 1, 0;0, 1, 1, 0, 0, 0;0, 0, 1, 1, 0, 0][ 0, 0, 0, 0, 0, 0;0, 0, 0, 0, 0, 0;0, 0, 1, 1, 0, 0;0, 0, 1, 0, 0, 0;0, 0, 0, 0, 0, 0;0, 0, 0, 0, 0, 0]
3.3、膨脹
與腐蝕相反,膨脹則是求局部最大值的操作。經過膨脹操作后,圖像中的高亮區域會擴大,就像受熱膨脹一樣。膨脹運算原理如下,原圖像中標有1的像素為高亮區域,結構元素中心的像素為錨點。進行膨脹操作時用結構元素掃描原圖像,用結構元素與覆蓋區域的像素進行與運算,如果所有像素的運算結構都是0,則該像素值為0,否則為1。膨脹運算前高亮區域有15像素,經過膨脹操作后擴充為29像素,所以膨脹的結果讓高亮區域長胖了。
//原圖像
0 0 0 0 0 0
0 0 1 1 1 0
0 1 1 1 1 0
0 1 1 1 1 0
0 1 1 0 0 0
0 0 1 1 0 0//結構元素
0 1 0
1 1 1
0 1 0//膨脹運算結果
0 0 1 1 1 0
0 1 1 1 1 1
1 1 1 1 1 1
1 1 1 1 1 1
1 1 1 1 1 0
0 1 1 1 1 0
//用特定的結構元素對圖像進行膨脹操作
void Imgproc.dilate(Mat src, Mat dst, Mat kernel, Point anchor, int iterations)
- src:輸入圖像,通道數任意,但深度應為CV_8U、CV_16U、CV_16S、CV_32F或CV_64F
- dst:輸出圖像,和src具有相同的尺寸和數據類型
- kernel:用于膨脹操作的結構元素,可以用getStructuringElement()函數生成
- anchor:結構元素內錨點的位置,默認(-1, -1),表示錨點位于結構元素中心
- iterations:膨脹的次數
public class Dilate {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//讀取圖像并顯示Mat src = Imgcodecs.imread("F:/IDEAworkspace/opencv/src/main/java/demo/wang.png");HighGui.imshow("src", src);HighGui.waitKey(0);//將圖像反向并顯示Core.bitwise_not(src, src);HighGui.imshow("negative", src);HighGui.waitKey(0);//生成十字型結構元素Mat kernel = Imgproc.getStructuringElement(1, new Size(3, 3));Point anchor = new Point(-1, -1);//膨脹操作1次并顯示Mat dst = new Mat();Imgproc.dilate(src, dst, new Mat());HighGui.imshow("dilate=1", dst);HighGui.waitKey(0);//腐蝕操作3次并顯示Imgproc.dilate(src, dst, kernel, anchor, 3);HighGui.imshow("dilate=3", dst);HighGui.waitKey(0);System.exit(0);}
}
原圖:
反向:
膨脹一次:
膨脹三次:
四、形態學操作
腐蝕和膨脹操作時圖像形態學的基礎。通過對腐蝕和膨脹操作進行不同的組合可以實現圖像的開運算、閉運算、形態學梯度、頂帽運算、黑帽運算和擊中擊不中等操作。
這些操作在OpenCV中都使用morphologyEx()函數實現,只是其中的參數不同。該函數如下:
//對圖像進行基于腐蝕和膨脹的高級形態學操作
void Imgproc.morphologyEx(Mat src, Mat dst, int op, Mat kernel, Point anchor, int iterations)
- src:輸入圖像,通道數任意,但深度應為CV_8U、CV_16U、CV_16S、CV_32F或CV_64F
- dst:輸出圖像,和src具有相同的尺寸和數據類型
- op:形態學操作的類型,具體有以下幾種:
- Imgproc.MORPH_ERODE:腐蝕操作
- Imgproc.MORPH_DILATE:膨脹操作
- Imgproc.MORPH_OPEN:開運算
- Imgproc.MORPH_CLOSE:閉運算
- Imgproc.MORPH_GRADIENT:形態學梯度
- Imgproc.MORPH_TOPHAT:頂帽運算
- Imgproc.MORPH_BLACKHAT:黑帽運算
- Imgproc.MORPH_HITMISS:擊中擊不中。只支持CV_8UC1類型的二值圖像
- kernel:結構元素,可以用getStructuringElement函數生成
- anchor:結構元素內錨點的位置,負數表示錨點位于結構元素中心
- iterations:腐蝕和膨脹的次數
4.1、開運算和閉運算
開運算是對圖像先腐蝕后膨脹的過程,它可以用來去除噪聲、去除細小的形狀(如毛刺)或在輕微連接處分離物體等。腐蝕操作同樣能去掉毛刺,但是腐蝕操作后高亮區域整個廋了一圈,形態發生了明顯變化,而開運算能在去掉毛刺的同時又保持原來的大小。
與開運算相反的操作是閉預算。閉運算是對圖像先膨脹后腐蝕的過程。閉算遠可以去除小型空洞,還能將狹窄的缺口連接起來。
public class MorphologyEx1 {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//讀取圖像并顯示Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/butterfly2.png");HighGui.imshow("src", src);HighGui.waitKey(0);Mat dst = new Mat();//閉運算1次并顯示Point anchor = new Point(-1, -1);Imgproc.morphologyEx(src, dst, Imgproc.MORPH_CLOSE, new Mat(), anchor, 1);HighGui.imshow("Close-1", dst);HighGui.waitKey(0);//閉運算3次并顯示Imgproc.morphologyEx(src, dst, Imgproc.MORPH_CLOSE, new Mat(), anchor, 3);HighGui.imshow("Close-3", dst);HighGui.waitKey(0);//在3次閉運算的基礎上進行開運算并顯示Imgproc.morphologyEx(dst, dst, Imgproc.MORPH_OPEN, new Mat(), anchor, 1);HighGui.imshow("Open", dst);HighGui.waitKey(0);System.exit(0);}
}
原圖:
閉運算1次:
閉運算3次:
開運算一次:
輸入圖像是一只蝴蝶的輪廓圖,經過1次閉運算后部分空洞消失,3次閉運算后大量空洞消失,在此基礎上進行1次開運算使很多輪廓線消失。
4.2、頂帽和黑帽
頂帽運算也稱為禮貌運算,是計算原圖像與開運算結果之差的操作。由于開運算后放大了裂縫或者局部低亮度的區域,從原圖中減去開運算后的圖像后就突出了比原圖像輪廓周邊區域更明亮的區域。
黑帽運算則是計算閉運算結果與原圖像之差的操作。黑帽運算后突出了比原圖像輪廓周邊區域更暗的區域。
public class MorphologyEx2 {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//讀取圖像并顯示Mat src = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/shaomai.png");HighGui.imshow("src", src);HighGui.waitKey(0);Mat dst = new Mat();//轉換為二值圖并顯示Imgproc.threshold(src, src, 120, 255, Imgproc.THRESH_BINARY);HighGui.imshow("Binary", src);HighGui.waitKey(0);//頂帽運算3次并顯示Point anchor = new Point(-1, -1);Imgproc.morphologyEx(src, dst, Imgproc.MORPH_TOPHAT, new Mat(), anchor, 3);HighGui.imshow("Tophat", dst);HighGui.waitKey(0);//黑帽運算3次并顯示Imgproc.morphologyEx(src, dst, Imgproc.MORPH_BLACKHAT, new Mat(), anchor, 3);HighGui.imshow("Blackhat", dst);HighGui.waitKey(0);System.exit(0);}
}
原圖:
二值圖:
頂帽3次:
黑帽3次:
4.3、形態學梯度
形態學梯度是計算膨脹結果與腐蝕結果之差的操作,其結果看上去就像圖像的輪廓。
public class MorphologyEx3 {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//讀取圖像1并顯示Mat src1 = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/shaomai.png");HighGui.imshow("src1", src1);HighGui.waitKey(0);//形態學梯度并顯示Point anchor = new Point(-1, -1);Mat dst = new Mat();Imgproc.morphologyEx(src1, dst, Imgproc.MORPH_GRADIENT, new Mat(), anchor, 1);HighGui.imshow("Gradient1", dst);HighGui.waitKey(0);//讀取圖像2并顯示Mat src2 = Imgcodecs.imread("/Users/acton_zhang/J2EE/MavenWorkSpace/opencv_demo/src/main/java/demo1/wang.png", Imgcodecs.IMREAD_GRAYSCALE);HighGui.imshow("src2", src2);HighGui.waitKey(0);//形態學梯度并顯示Imgproc.morphologyEx(src2, dst, Imgproc.MORPH_GRADIENT, new Mat(), anchor, 1);HighGui.imshow("Gradient2", dst);HighGui.waitKey(0);System.exit(0);}
}
原圖1:
形態學梯度:
原圖2:
形態學梯度:
4.4、擊中擊不中
擊中擊不中運算常用于二值圖像,它的要求比腐蝕操作還要嚴格。只有當結構元素與其覆蓋的區域完全相同時,該像素才為1,否則為0,如下:
//原圖
0 0 0 0 0 0
0 1 1 1 1 0
0 1 1 0 1 0
0 1 1 1 1 1
0 0 0 0 0 0//結構元素
1 1 1
1 0 1
1 1 1//擊中擊不中結果
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 1 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
public class HitMiss {static {OpenCV.loadLocally(); // 自動下載并加載本地庫}public static void main(String[] args) {//構建一個6*6的小矩陣并導入數據Mat src = new Mat(6, 6, CvType.CV_8UC1);byte[] data = new byte[] {0, 0, 0, 0, 0, 0,0, 0, 1, 1, 1, 0,0, 1, 1, 1, 1, 0,0, 1, 1, 1, 1, 0,0, 1, 1, 0, 0, 0,0, 0, 1, 1, 0, 0};src.put(0, 0, data);//打印矩陣輸出System.out.println(src.dump());System.out.println();//構建矩形結構元素并輸出Mat kernel = Imgproc.getStructuringElement(0, new Size(3, 3));System.out.println(kernel.dump());System.out.println();//擊中擊不中測試并輸出Point anchor = new Point(-1, -1);Mat dst = new Mat(6, 6, CvType.CV_8UC1);Imgproc.morphologyEx(src, dst, Imgproc.MORPH_HITMISS, kernel, anchor, 1);System.out.println(dst.dump());}
}
[ 0, 0, 0, 0, 0, 0;0, 0, 1, 1, 1, 0;0, 1, 1, 1, 1, 0;0, 1, 1, 1, 1, 0;0, 1, 1, 0, 0, 0;0, 0, 1, 1, 0, 0][ 1, 1, 1;1, 1, 1;1, 1, 1][ 0, 0, 0, 0, 0, 0;0, 0, 0, 0, 0, 0;0, 0, 0, 1, 0, 0;0, 0, 0, 0, 0, 0;0, 0, 0, 0, 0, 0;0, 0, 0, 0, 0, 0]