OpenCV——圖像形態學

圖像形態學

  • 一、像素的距離
  • 二、像素的鄰域
  • 三、膨脹與腐蝕
    • 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]

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

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

相關文章

在Django中把Base64字符串保存為ImageField

在數據model中使用ImageField來管理avatar。 class User(models.Model):AVATAR_COLORS ((#212736, Black),(#2161FD, Blue),(#36B37E, Green),(#F5121D, Red),(#FE802F, Orange),(#9254DE, Purple),(#EB2F96, Magenta),)def generate_filename(self, filename):url "av…

使用 R 處理圖像

在 R 中進行圖像處理&#xff0c;使用像 imager 這樣的包&#xff0c;可以實現強大的數字圖像分析和處理。本博客將基于"圖像數據分析"文檔的概念&#xff0c;演示使用 imager 包進行的關鍵技術——圖像增強、去噪和直方圖均衡化&#xff0c;并通過可視化結果展示這些…

一命速通Prometheus+Grafana+Consul+VictoriaMetrics

Prometheus業務 搭建及使用 注意&#xff1a;優先看完提供的博客鏈接&#xff0c;可以快速了解該工具的功能及其搭建和使用。 prometheusgrafana 一、PrometheusGrafana普羅米修斯&#xff0c;搭建和使用_普羅米修斯 grafana-CSDN博客 ./prometheus --config.fileprometheus.ym…

螞蟻百寶箱快速創建智能體AI小程序

螞蟻百寶箱官網https://tbox.alipay.com/community?operationSource1006/ 以下是一篇關于螞蟻百寶箱快速創建智能體 AI 小程序的圖文并茂的博客&#xff1a; 標題&#xff1a;螞蟻百寶箱快速創建智能體 AI 小程序&#xff0c;開啟智能應用新體驗 引言 在數字化飛速發展的當…

大模型面試題:RL Scaling Law 中的“過優化”現象及其緩解方法是啥?

更多面試題&#xff0c;請看 大模型面試題總結-CSDN博客 或者 https://gitee.com/lilitom/ai_interview_questions/blob/master/README.md 最好將URL復制到瀏覽器中打開&#xff0c;不然可能無法直接打開 ---------------------------------------------------------------…

Filecoin系列 - IPLD 技術分析

1. 用途 1.1 存儲數據 為了成功地將數據加到 Filecoin 網絡, 需要成功完成以下步驟: 客戶端導入數據生成CAR文件: 數據必須打包成 CAR file (內容可尋址檔案) - CAR是IPLD規范的序列化歸檔文件.存儲交易: 存儲供應商和客戶之間的存儲交易必須由客戶發起, 并由存儲供應商接受…

Apptrace如何幫我精準追蹤移動廣告效果?

開發者視角&#xff1a;Apptrace如何幫我精準追蹤移動廣告效果&#xff1f;?? 作為獨立開發者&#xff0c;我最頭疼的就是?“廣告投放到底有沒有用&#xff1f;”?——錢花出去了&#xff0c;用戶是刷量機器人還是真實用戶&#xff1f;哪個渠道的ROI最高&#xff1f;Apptr…

【MySQL篇07】:redo log日志與buffer pool詳解

文章目錄 1. Buffer Pool 緩沖池2. redo log (重做日志)redo log 的作用&#xff1a;為什么需要 redo log buffer&#xff1f;什么時候刷盤呢&#xff1f; 3. 總結一下 redo log 和 Buffer Pool 在更新數據時的協同工作關鍵組件關系圖刷盤完成后 1. Buffer Pool 緩沖池 首先&a…

Qt Library庫系列----Serial串口

前言 每次寫串口相關的功能時&#xff0c;總是需要重新寫或者復制原來寫過的文件&#xff0c;容易出錯不說&#xff0c;這也不是碼農的風格&#xff0c;所以還是得有一套自己得代碼庫&#xff0c;方便調用&#xff0c;又能保持神秘感。 一、開發需求 1.有個實例類&#xff1b;…

第八節:Vben Admin 最新 v5.0 (vben5) 快速入門 - 用戶管理(下)

Vben5 系列文章目錄 ?? 基礎篇 ? 第一節:Vben Admin 最新 v5.0 (vben5) 快速入門 ? 第二節:Vben Admin 最新 v5.0 (vben5) 快速入門 - Python Flask 后端開發詳解(附源碼) ? 第三節:Vben Admin 最新 v5.0 (vben5) 快速入門 - 對接后端登錄接口(上) ? 第四節:Vben Ad…

Redis 性能瓶頸時如何處理?

當 Redis 遇到性能瓶頸時&#xff0c;需要從多個維度進行排查和優化。以下是系統化的解決方案&#xff0c;涵蓋硬件、配置、數據模型、網絡等關鍵點&#xff1a; 一、硬件資源優化 內存瓶頸 現象&#xff1a;頻繁觸發 OOM 或 used_memory 接近物理內存。解決&#xff1a; 升級服…

多相機三維人臉掃描儀:超寫實數字人模型制作“加速器”

超寫實數字人&#xff0c;又稱“數字分身”&#xff0c;是以真人形象為原型構建的高仿真虛擬形象&#xff0c;按維度可分為2D數字人與3D數字人。這類數字人已廣泛應用于影視制作、游戲交互、品牌直播等場景&#xff0c;其核心價值在于通過技術手段實現真人形象的數字化復刻&…

ceph 自動調整 pg_num

要讓 Ceph 的 pool 自動調整 pg_num(PG 數量),你需要啟用 PG autoscaler。這是從 Ceph Octopus(15.x) 開始引入的功能,能根據池的容量和對象數量自動建議或調整 pg_num,以實現負載均衡。 ? 一步步開啟 Pool 的 pg_num 自動調整 1. 啟用 PG autoscaler 模塊(通常默認啟…

Python Beautiful Soup 4【HTML/XML解析庫】 簡介

全面剖析大模型 圖解大模型&#xff1a;生成式AI原理與實戰 大語言模型大模型應用開發Transformer DeepSeek模型原理開發深度學習 圖靈出品 大模型強化學習詳解 大模型算法&#xff1a;強化學習、微調與對齊&#xff08;全彩&#xff09;詳解強化學習 RLHF GRPO DPO SFT CoT D…

AI Agent開發與安全

AI Agent的核心演進 Level 1&#xff1a;LLM Agent&#xff08;聊天機器人&#xff09; 特點&#xff1a;靠提示詞工程賦予人設&#xff08;如星座占卜、角色扮演&#xff09;&#xff0c;但存在幻覺問題&#xff0c;輸出不可控。局限&#xff1a;娛樂性強&#xff0c;難勝任嚴…

NumPy玩轉數據科學

本文在創作過程中借助 AI 工具輔助資料整理與內容優化。圖片來源網絡。 文章目錄 一、引言二、NumPy 概述2.1 NumPy 的定義與發展2.2 NumPy 的重要性 三、NumPy 的多維數組支持3.1 多維數組的概念3.2 多維數組的創建與操作3.2.1 數組的創建3.2.2 數組的索引和切片3.2.3 數組的運…

【uniapp小程序開發】圖表組件ucharts的使用(入門)

一、插件的安裝 安裝非常簡單&#xff0c;打開uniapp的插件市場&#xff0c;導入到項目中即可 下載地址&#xff1a;https://ext.dcloud.net.cn/plugin?id271 二、開始實踐 先看頁面的效果 頁面中實現了三個基本圖形的展示&#xff1a;折線圖、餅圖和柱狀圖。 上圖左一&a…

APISIX+etcd高可用集群部署方案詳解

#作者&#xff1a;任少近 文章目錄 一、背景二、部署etcd1、etcd的svc部署yaml2、Etcd 服務定義說明3、etcd的statefulset部署yaml4、Etcd 狀態集&#xff08;StatefulSet&#xff09;配置說明5、查看集群狀態 三、部署apisix的deployment部署1、apisix部署yaml文件2、APISIX …

Excel常用公式大全

資源寶整理分享&#xff1a;https://www.httple.net Excel常用公式大全可以幫助用戶提高工作效率&#xff0c;掌握常用的Excel公式&#xff0c;讓數據處理和計算工作更加便捷高效。了解公式學習方法、用途&#xff0c;不再死記硬背&#xff0c;拒絕漫無目的。 命令用途注釋說…

什么是Seata?

深入解析Seata&#xff1a;分布式事務的終極解決方案 什么是Seata&#xff1f; Seata&#xff08;Simple Extensible Autonomous Transaction Architecture&#xff09;是一款開源的分布式事務解決方案&#xff0c;由阿里巴巴中間件團隊于2019年1月發起并開源&#xff08;最初…