图像形态学

  • 一、像素的距离
  • 二、像素的邻域
  • 三、膨胀与腐蚀
    • 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;最初…