实用指南:学习 Android (二十) 学习 OpenCV (五)

学习 Android (二) 学习 OpenCV (五)

在上一章节,我们对于 OpenCV 中的边缘检测有了进一步的学习和了解,这一章节我们将对图像形态学操作进行学习和了解;

26. 图像腐蚀

26.1 什么是图像腐蚀

腐蚀(Erosion)是图像处理中一种基本的形态学操作,它通过使用结构元素(内核)对图像进行扫描,根据结构元素与图像的交集关系来"收缩"或"细化"图像中的前景对象。

  • 数学定义

    从数学角度,腐蚀操作可以定义为:

    A ⊖ B = {z | (B)z ⊆ A}

    其中:

    • A 是输入图像(二值图像或灰度图像)

    • B 是结构元素(内核)

    • (B)z 表示将结构元素B平移z

    • 结果是所有使B完全包含在A中的平移位置z的集合

  • 操作过程

    腐蚀操作的工作流程:

    1. 定义结构元素:选择一个形状和大小合适的结构元素(内核)

    2. 扫描图像:将结构元素的中心对准图像中的每个像素

    3. 应用规则

      • 对于二值图像:如果结构元素覆盖的所有像素都是前景(1),则中心像素保留为前景,否则变为背景(0)

        原始图像:
        [1, 1, 1, 0, 0]
        [1, 1, 1, 0, 0]
        [1, 1, 1, 0, 0]
        [0, 0, 0, 0, 0]
        [0, 0, 0, 0, 0]
        3×3 矩形结构元素
        结构元素:
        [1, 1, 1]
        [1, 1, 1]
        [1, 1, 1]
        腐蚀过程
        我们可以理解为,原始图像中,以每个像素坐标为中心点,能够完全映射结构元素,即像素保留前期1,否则变为背景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]
      • 对于灰度图像:取结构元素覆盖区域内像素的最小值作为中心像素的值

        原始图像:
        [90	85 80 75 70]
        [95	90 85 80 75]
        [100 95	90 85 80]
        [105 100 95	90 85]
        [110 105 100 95	90]
        3×3 矩形结构元素
        结构元素:
        [1, 1, 1]
        [1, 1, 1]
        [1, 1, 1]
        腐蚀过程
        和二值图像过程一样,不过是将能够完全映射结构元素的中心点,变为该映射矩阵中最小的值,需要留意的是,在切换中心点进行下一个腐蚀操作时,映射到的矩阵的值要以原始图像为准,不能带入上一个已经腐蚀过的中心点值
        腐蚀结果
        [×	×	×	×	×]
        [×	80	75	70	×]
        [×	85	80	75	×]
        [×	90	85	80	×]
        [×	×	×	×	×]
    4. 输出结果:生成腐蚀后的图像

  • 结构元素

    结构元素是腐蚀操作的核心,常见的形状有:

    • 矩形:MORPH_RECT

    • 椭圆形:MORPH_ELLIPSE

    • 十字形:MORPH_CROSS

    结构元素的大小决定了腐蚀的程度,较大的结构元素会产生更强烈的腐蚀效果。

26.2 关键代码分析

腐蚀核心函数:

Imgproc.erode(binaryMat, erosionMat, kernel, new Point(-1, -1), ITERATIONS);

参数说明:

  • binaryMat: 输入图像(二值图像或灰度图像)

  • erosionMat: 输出图像

  • kernel: 结构元素(定义腐蚀操作的形状和大小)

  • new Point(-1, -1): 锚点位置(默认中心点)

  • ITERATIONS: 迭代次数(腐蚀操作执行的次数)

结构元素创建:

Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(KERNEL_SIZE, KERNEL_SIZE));

结构元素类型:

  • Imgproc.MORPH_RECT: 矩形结构元素 : 产生规则的腐蚀效果,各方向均匀收缩

  • Imgproc.MORPH_ELLIPSE: 椭圆形结构元素 : 产生更自然的圆形腐蚀效果

  • Imgproc.MORPH_CROSS: 十字形结构元素 : 主要影响水平和垂直方向

结构元素大小:

  • 较小尺寸:轻微腐蚀,保留更多细节

  • 较大尺寸:强烈腐蚀,可能丢失重要特征

迭代次数的影响:

  • 较少迭代:轻微腐蚀效果

  • 多次迭代:累积腐蚀效果,相当于使用更大的结构元素

处理流程:

  1. 图像加载:从资源文件加载图像

  2. 预处理:转换为灰度图并进行二值化,创建明显的目标对象

  3. 创建结构元素:定义腐蚀操作使用的内核形状和大小

  4. 应用腐蚀:使用erode函数对图像进行腐蚀操作

  5. 结果显示:将原图和腐蚀结果显示在ImageView中

26.2 应用场景

腐蚀操作在图像处理中有多种应用场景:

  1. 去除小噪声点

    • 消除图像中的孤立小白点(椒盐噪声)

    • 断开细小的连接

  2. 分离相连物体

    • 将轻微接触的物体分离开

    • 减少物体尺寸以便更好地计数

  3. 边缘检测预处理

    • 先腐蚀再膨胀(开运算)可以平滑物体轮廓

    • 去除细小毛刺

  4. 文本图像处理

    • 使文本笔画变细,提高OCR识别率

    • 分离粘连字符

  5. 医学图像处理

    • 分离接触的细胞或组织

    • 提取特定形状的结构

特点

  • 使前景对象变小

  • 消除小的孤立点

  • 平滑对象边界

  • 对二值图像和灰度图像都有效

26.3 示例

ErosionActivity.java

public class ErosionActivity
extends AppCompatActivity {
private ActivityErosionBinding mBinding;
static {
System.loadLibrary("opencv_java4");
}
private Mat originalMat;
private Mat binaryMat;
// 新增:用于存储二值化图像
private Mat erosionRectMat;
private Mat erosionEllipseMat;
private Mat erosionCrossMat;
private Mat grayMat;
// 腐蚀参数
private static final int KERNEL_SIZE = 4;
private static final int ITERATIONS = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = ActivityErosionBinding.inflate(getLayoutInflater());
setContentView(mBinding.getRoot());
try {
// 加载原始图像
originalMat = Utils.loadResource(this, R.drawable.lxh);
if (originalMat == null || originalMat.empty()) {
Toast.makeText(this, "Failed to load image", Toast.LENGTH_SHORT).show();
return;
}
// 显示原图
OpenCVHelper.showMat(mBinding.ivOriginal, originalMat);
// 转换为灰度图
grayMat = new Mat();
Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_BGR2GRAY);
// 二值化处理,创建明显的目标对象
binaryMat = new Mat();
// 使用新的Mat对象存储二值化结果
Imgproc.threshold(grayMat, binaryMat, 127, 255, Imgproc.THRESH_BINARY);
// 应用不同类型的腐蚀
applyRectErosion();
applyEllipseErosion();
applyCrossErosion();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "Error: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
/**
* 应用矩形结构元素的腐蚀操作
*/
private void applyRectErosion() {
applyErosion(Imgproc.MORPH_RECT, mBinding.ivErosionRect, "矩形");
}
/**
* 应用椭圆结构元素的腐蚀操作
*/
private void applyEllipseErosion() {
applyErosion(Imgproc.MORPH_ELLIPSE, mBinding.ivErosionEllipse, "椭圆");
}
/**
* 应用十字形结构元素的腐蚀操作
*/
private void applyCrossErosion() {
applyErosion(Imgproc.MORPH_CROSS, mBinding.ivErosionCross, "十字形");
}
/**
* 通用的腐蚀操作方法
*
* @param shape 结构元素形状
* @param imageView 显示结果的ImageView
* @param shapeName 形状名称(用于错误提示)
*/
private void applyErosion(int shape, ImageView imageView, String shapeName) {
Mat kernel = null;
Mat resultMat = null;
try {
// 创建结构元素
kernel = Imgproc.getStructuringElement(shape, new Size(KERNEL_SIZE, KERNEL_SIZE));
// 创建结果Mat
resultMat = new Mat();
// 应用腐蚀操作
Imgproc.erode(binaryMat, resultMat, kernel, new Point(-1, -1), ITERATIONS);
// 显示结果
OpenCVHelper.showMat(imageView, resultMat);
// 根据形状保存结果
switch (shape) {
case Imgproc.MORPH_RECT:
erosionRectMat = resultMat;
break;
case Imgproc.MORPH_ELLIPSE:
erosionEllipseMat = resultMat;
break;
case Imgproc.MORPH_CROSS:
erosionCrossMat = resultMat;
break;
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, shapeName + "腐蚀操作失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
// 确保异常时也释放资源
if (resultMat != null) {
resultMat.release();
}
} finally {
// 释放内核资源
if (kernel != null) {
kernel.release();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// 释放所有Mat资源
if (originalMat != null) OpenCVHelper.safeRelease(originalMat);
if (binaryMat != null) OpenCVHelper.safeRelease(binaryMat);
if (grayMat != null) OpenCVHelper.safeRelease(grayMat);
if (erosionRectMat != null) OpenCVHelper.safeRelease(erosionRectMat);
if (erosionEllipseMat != null) OpenCVHelper.safeRelease(erosionEllipseMat);
if (erosionCrossMat != null) OpenCVHelper.safeRelease(erosionCrossMat);
}
}

在这里插入图片描述
在这里插入图片描述

作者的示例图并不能很明显的体现出各个结构元素模式的差异,希望读者能够自己处理(不是因为作者懒得去找图)

27. 图像膨胀

27.1 什么是图像膨胀

图像膨胀(Dilation)是形态学操作中的一种基本操作,与腐蚀操作相反。它通过使用结构元素(内核)对图像进行扫描,根据结构元素与图像的并集关系来"扩大"或"增厚"图像中的前景对象。

  • 数学定义

    膨胀操作可以定义为:

    A ⊕ B = {z | (B)z ∩ A ≠ ∅}

    其中:

    • A 是输入图像(二值图像或灰度图像)

    • B 是结构元素(内核)

    • (B)z 表示将结构元素B平移z

    • 结果是所有使B与A有交集的平移位置z的集合

  • 操作过程

    膨胀操作的工作流程完全和腐蚀相反:

    1. 定义结构元素:选择一个形状和大小合适的结构元素(内核)

    2. 扫描图像:将结构元素的中心对准图像中的每个像素

    3. 应用规则

      • 对于二值图像:如果结构元素覆盖的区域中至少有一个像素是前景(1),则中心像素设置为前景(1)

      • 对于灰度图像:取结构元素覆盖区域内像素的最大值作为中心像素的值

    4. 输出结果:生成膨胀后的图像

  • 与腐蚀操作的关系

    膨胀和腐蚀是形态学操作中的一对基本操作:

    • 腐蚀:缩小前景区域,消除小物体

    • 膨胀:扩大前景区域,填充小孔洞

    • 两者结合可以形成更复杂的形态学操作(如开运算、闭运算)

27.2 关键代码分析

函数原型

public static void dilate(Mat src, Mat dst, Mat kernel, Point anchor, int iterations, int borderType, Scalar borderValue)

参数详解

  • src:输入图像,可以是二值或灰度图像

  • dst:输出图像,与输入图像相同的尺寸和类型

  • kernel:结构元素,可以通过getStructuringElement创建

  • anchor:锚点位置,默认为中心点(-1, -1)

  • iterations:膨胀操作的迭代次数

  • borderType:边界处理类型(如BORDER_CONSTANT

  • borderValue:边界值,当边界类型为BORDER_CONSTANT时使用

27.3 应用场景

膨胀操作在图像处理中有多种应用场景:

连接断裂部分

  • 修复断裂的文字笔画

  • 连接断开的边缘线

填充小孔洞

  • 填充物体内部的小孔

  • 平滑物体边界

增加物体尺寸

  • 使细小的物体变得更明显

  • 增加边缘厚度

与腐蚀操作结合

  • 开运算:先腐蚀后膨胀,用于去除小物体和平滑边界

  • 闭运算:先膨胀后腐蚀,用于填充小孔和连接断点

边缘检测后处理

  • 使检测到的边缘更加连续

  • 增加边缘的明显度

27.4 示例

DilationDemoActivity.java

public class DilationDemoActivity
extends AppCompatActivity {
private ActivityDilationDemoBinding mBinding;
static {
System.loadLibrary("opencv_java4");
}
private Mat originalMat;
private Mat binaryMat;
// 新增:用于存储二值化图像
private Mat dilationRectMat;
private Mat dilationEllipseMat;
private Mat dilationCrossMat;
private Mat grayMat;
// 膨胀参数
private static final int KERNEL_SIZE = 3;
private static final int ITERATIONS = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = ActivityDilationDemoBinding.inflate(getLayoutInflater());
setContentView(mBinding.getRoot());
try {
originalMat = Utils.loadResource(this, R.drawable.lxh);
if (originalMat.empty()) {
Toast.makeText(this, "Failed to load image", Toast.LENGTH_SHORT).show();
return;
}
showMat(mBinding.ivOriginal, originalMat);
// 转换为灰度图
grayMat = new Mat();
Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_BGR2GRAY);
// 二值化处理,创建明显的目标对象
binaryMat = new Mat();
// 使用新的Mat对象存储二值化结果
Imgproc.threshold(grayMat, binaryMat, 127, 255, Imgproc.THRESH_BINARY);
// 应用不同类型的腐蚀
applyRectErosion();
applyEllipseErosion();
applyCrossErosion();
} catch (Exception e) {
e.printStackTrace();
}
}
private void applyRectErosion() {
try {
// 创建结构元素(内核)
// 使用矩形结构元素,大小5×5
Mat kernel = Imgproc.getStructuringElement(
Imgproc.MORPH_RECT,
new Size(KERNEL_SIZE, KERNEL_SIZE)
);
// 应用膨胀操作
// 参数说明:
// - binaryMat: 输入图像(二值图像)
// - dilationMat: 输出图像
// - kernel: 结构元素
// - anchor: 锚点位置(默认中心点(-1,-1))
// - ITERATIONS: 迭代次数(膨胀操作的执行次数)
dilationRectMat = new Mat();
Imgproc.dilate(binaryMat, dilationRectMat, kernel, new Point(-1, -1), ITERATIONS);
// 显示结果
showMat(mBinding.ivDilationRect, dilationRectMat);
} catch (Exception e) {
e.printStackTrace();
Log.e("TAG", "Error in dilation operation: " + e.getMessage());
}
}
private void applyEllipseErosion() {
try {
Mat kernel = Imgproc.getStructuringElement(
Imgproc.MORPH_ELLIPSE,
new Size(KERNEL_SIZE, KERNEL_SIZE)
);
dilationEllipseMat = new Mat();
Imgproc.dilate(binaryMat, dilationEllipseMat, kernel, new Point(-1, -1), ITERATIONS);
// 显示结果
showMat(mBinding.ivDilationEllipse, dilationEllipseMat);
} catch (Exception e) {
e.printStackTrace();
Log.e("TAG", "Error in dilation operation: " + e.getMessage());
}
}
private void applyCrossErosion() {
try {
Mat kernel = Imgproc.getStructuringElement(
Imgproc.MORPH_CROSS,
new Size(KERNEL_SIZE, KERNEL_SIZE)
);
dilationCrossMat = new Mat();
Imgproc.dilate(binaryMat, dilationCrossMat, kernel, new Point(-1, -1), ITERATIONS);
// 显示结果
showMat(mBinding.ivDilationCross, dilationCrossMat);
} catch (Exception e) {
e.printStackTrace();
Log.e("TAG", "Error in dilation operation: " + e.getMessage());
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (originalMat != null) OpenCVHelper.safeRelease(originalMat);
if (binaryMat != null) OpenCVHelper.safeRelease(binaryMat);
if (dilationRectMat != null) OpenCVHelper.safeRelease(dilationRectMat);
if (dilationEllipseMat != null) OpenCVHelper.safeRelease(dilationEllipseMat);
if (dilationCrossMat != null) OpenCVHelper.safeRelease(dilationCrossMat);
if (grayMat != null) OpenCVHelper.safeRelease(grayMat);
}
}

在这里插入图片描述

在这里插入图片描述

28. 开运算与闭运算

开运算(Opening)和闭运算(Closing)是形态学图像处理中的两种重要操作,它们都是由基本的腐蚀和膨胀操作组合而成的。

28.1 什么是开运算

定义:先腐蚀后膨胀

开运算(A) = 膨胀(腐蚀(A))
A ∘ B = (A ⊖ B) ⊕ B

作用

  • 消除小物体(在腐蚀阶段被移除)

  • 平滑物体轮廓(在膨胀阶段部分恢复)

  • 断开细小的连接

  • 保持大体形状不变

28.2 什么是闭运算

定义:先膨胀后腐蚀

闭运算(A) = 腐蚀(膨胀(A))
A • B = (A ⊕ B) ⊖ B

作用

  • 填充小孔洞(在膨胀阶段被填充)

  • 连接邻近物体

  • 平滑轮廓

  • 保持大体形状不变

28.3 关键代码分析

OpenCV提供了专门的函数来进行开运算和闭运算:

// 形态学操作通用函数
public static void morphologyEx(Mat src, Mat dst, int op, Mat kernel, Point anchor, int iterations, int borderType, Scalar borderValue)

参数详解

  • src:输入图像

  • dst:输出图像

  • op:操作类型

    • Imgproc.MORPH_OPEN:开运算

    • Imgproc.MORPH_CLOSE:闭运算

  • kernel:结构元素

  • anchor:锚点位置(默认-1,-1)

  • iterations:操作次数

  • borderType:边界类型

  • borderValue:边界值

28.4 应用场景

  • 开运算的应用场景

    1. 去除小噪声点

      • 消除图像中的孤立小点

      • 去除椒盐噪声

    2. 物体分离

      • 分离轻微接触的物体

      • 断开细小的连接

    3. 边缘平滑

      • 平滑物体边界

      • 去除毛刺

    4. 文本处理

      • 去除文档图像中的小噪点

      • 改善OCR识别效果

  • 闭运算的应用场景

    1. 填充孔洞

      • 填充物体内部的小孔

      • 修复不完整的轮廓

    2. 连接断裂

      • 连接断开的边缘

      • 修复断裂的文字笔画

    3. 平滑轮廓

      • 使轮廓更加连续

      • 减少轮廓上的缺口

    4. 前景提取

      • 填充前景物体中的小空隙

      • 改善分割结果

28.5 示例

MorphologyOpsActivity.java

public class MorphologyOpsActivity
extends AppCompatActivity {
private ActivityMorphologyOpsBinding mBinding;
static {
System.loadLibrary("opencv_java4");
}
private Mat originalMat;
// 形态学操作参数
private static final int KERNEL_SIZE = 5;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = ActivityMorphologyOpsBinding.inflate(getLayoutInflater());
setContentView(mBinding.getRoot());
try {
// 从资源加载图像
originalMat = Utils.loadResource(this, R.drawable.lena);
if (originalMat.empty()) {
Toast.makeText(this, "Failed to load image", Toast.LENGTH_SHORT).show();
return;
}
// 显示原图
showMat(mBinding.ivOriginal, originalMat);
// 创建测试图像(包含小物体和小孔洞)
Mat testImage = createTestImage();
// 应用开运算和闭运算
applyMorphologyOperations(testImage);
// 释放测试图像
testImage.release();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 创建包含小物体和小孔洞的测试图像
*/
private Mat createTestImage() {
// 转换为灰度图
Mat grayMat = new Mat();
Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_BGR2GRAY);
// 二值化处理
Mat binary = new Mat();
Imgproc.threshold(grayMat, binary, 127, 255, Imgproc.THRESH_BINARY);
// 添加一些小物体(噪声)
Mat withNoise = binary.clone();
addSmallObjects(withNoise);
// 添加一些小孔洞
Mat withHoles = withNoise.clone();
addSmallHoles(withHoles);
// 释放临时资源
grayMat.release();
binary.release();
withNoise.release();
return withHoles;
}
/**
* 添加小物体(模拟噪声)
*/
private void addSmallObjects(Mat image) {
// 在随机位置添加小白色方块(模拟噪声)
for (int i = 0; i <
20; i++) {
int x = (int) (Math.random() * (image.cols() - 5));
int y = (int) (Math.random() * (image.rows() - 5));
Imgproc.rectangle(image,
new Point(x, y),
new Point(x + 3, y + 3),
new Scalar(255), -1);
}
}
/**
* 添加小孔洞
*/
private void addSmallHoles(Mat image) {
// 在随机位置添加小黑色方块(模拟孔洞)
for (int i = 0; i <
15; i++) {
int x = (int) (Math.random() * (image.cols() - 5));
int y = (int) (Math.random() * (image.rows() - 5));
Imgproc.rectangle(image,
new Point(x, y),
new Point(x + 3, y + 3),
new Scalar(0), -1);
}
}
private void applyMorphologyOperations(Mat testImage) {
try {
// 显示结果
demonstrateKernelEffects(testImage);
} catch (Exception e) {
e.printStackTrace();
Log.e("TAG", "Error in morphology operations: " + e.getMessage());
}
}
/**
* 演示不同结构元素的影响
*/
private void demonstrateKernelEffects(Mat testImage) {
// 创建不同形状的结构元素
Mat rectKernel = Imgproc.getStructuringElement(
Imgproc.MORPH_RECT, new Size(KERNEL_SIZE, KERNEL_SIZE));
Mat ellipseKernel = Imgproc.getStructuringElement(
Imgproc.MORPH_ELLIPSE, new Size(KERNEL_SIZE, KERNEL_SIZE));
Mat crossKernel = Imgproc.getStructuringElement(
Imgproc.MORPH_CROSS, new Size(KERNEL_SIZE, KERNEL_SIZE));
Mat openRect = new Mat();
Mat openEllipse = new Mat();
Mat openCross = new Mat();
Mat closeRect = new Mat();
Mat closeEllipse = new Mat();
Mat closeCross = new Mat();
try {
// 应用不同结构元素的开运算
Imgproc.morphologyEx(testImage, openRect, Imgproc.MORPH_OPEN, rectKernel);
Imgproc.morphologyEx(testImage, openEllipse, Imgproc.MORPH_OPEN, ellipseKernel);
Imgproc.morphologyEx(testImage, openCross, Imgproc.MORPH_OPEN, crossKernel);
// 应用不同结构元素的闭运算
Imgproc.morphologyEx(testImage, closeRect, Imgproc.MORPH_CLOSE, rectKernel);
Imgproc.morphologyEx(testImage, closeEllipse, Imgproc.MORPH_CLOSE, ellipseKernel);
Imgproc.morphologyEx(testImage, closeCross, Imgproc.MORPH_CLOSE, crossKernel);
showMat(mBinding.ivOpen, openRect);
showMat(mBinding.ivClose, closeRect);
} finally {
// 释放资源
rectKernel.release();
ellipseKernel.release();
crossKernel.release();
openRect.release();
openEllipse.release();
openCross.release();
closeRect.release();
closeEllipse.release();
closeCross.release();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (originalMat != null) OpenCVHelper.safeRelease(originalMat);
}
}

在这里插入图片描述

29. 顶帽与黑帽

顶帽(Top-hat)和黑帽(Black-hat)是形态学图像处理中的两种高级操作,它们都是基于基础的开运算和闭运算衍生而来的。

29.1 什么是顶帽

定义:原图像与开运算结果的差

顶帽 = 原图像 - 开运算(原图像)
顶帽(A) = A - (A ∘ B)

物理意义

  • 突出比背景亮的细小物体

  • 增强图像中的亮细节特征

  • 在均匀背景上提取微小亮物体

29.2 什么是黑帽

定义:闭运算结果与原图像的差

黑帽 = 闭运算(原图像) - 原图像
黑帽(A) = (A • B) - A

物理意义

  • 突出比背景暗的细小物体

  • 增强图像中的暗细节特征

  • 在均匀背景上提取微小暗物体

29.3 关键代码分析

OpenCV使用统一的 morphologyEx 函数来实现顶帽和黑帽运算:

public static void morphologyEx(Mat src, Mat dst, int op, Mat kernel, Point anchor, int iterations, int borderType, Scalar borderValue)

参数详解

  • src:输入图像(通常是灰度图像)

  • dst:输出图像

  • op:操作类型

    • Imgproc.MORPH_TOPHAT:顶帽运算

    • Imgproc.MORPH_BLACKHAT:黑帽运算

  • kernel:结构元素

  • anchor:锚点位置(默认-1,-1)

  • iterations:操作次数(通常为1)

  • borderType:边界类型

  • borderValue:边界值

29.4 应用场景

  • 顶帽运算的应用场景

    1. 光照不均匀校正

      • 提取并去除不均匀的背景光照

      • 增强在明亮背景下的暗细节

    2. 微小物体检测

      • 检测图像中的小亮点、小物体

      • 工业检测中的缺陷检测

    3. 文本提取

      • 从复杂背景中提取文字

      • 增强文档图像中的文字区域

    4. 医学图像处理

      • 提取细胞图像中的微小结构

      • 增强X光图像中的细微特征

  • 黑帽运算的应用场景

    1. 暗细节增强

      • 突出图像中的暗区域和小孔洞

      • 增强阴影区域的细节

    2. 缺陷检测

      • 检测产品表面的暗缺陷

      • 工业质量控制的瑕疵检测

    3. 血管检测

      • 在医学图像中增强血管结构

      • 提取视网膜图像中的血管网络

    4. 目标提取

      • 从明亮背景中提取暗目标

      • 夜间图像中的目标检测

29.5 示例

HatTransformsActivity.java

public class HatTransformsActivity
extends AppCompatActivity {
private ActivityHatTransformsBinding mBinding;
static {
System.loadLibrary("opencv_java4");
}
private Mat originalMat, grayMat;
// 形态学操作参数
private static final int KERNEL_SIZE = 15;
private static final int ITERATIONS = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = ActivityHatTransformsBinding.inflate(getLayoutInflater());
setContentView(mBinding.getRoot());
try {
// 从资源加载图像
originalMat = Utils.loadResource(this, R.drawable.twgx);
if (originalMat.empty()) {
Toast.makeText(this, "Failed to load image", Toast.LENGTH_SHORT).show();
return;
}
// 转换为灰度图
grayMat = new Mat();
Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_BGR2GRAY);
// 显示原图
showMat(mBinding.ivOriginal, grayMat);
// 应用顶帽和黑帽运算
applyHatTransforms();
// 演示文本增强应用
demonstrateTextEnhancement();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "Error: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
private void applyHatTransforms() {
// 创建结构元素
Mat kernel = Imgproc.getStructuringElement(
Imgproc.MORPH_RECT,
new Size(KERNEL_SIZE, KERNEL_SIZE)
);
Mat tophatResult = new Mat();
Mat blackhatResult = new Mat();
try {
// 应用顶帽运算(突出亮细节)
Imgproc.morphologyEx(grayMat, tophatResult, Imgproc.MORPH_TOPHAT, kernel,
new Point(-1, -1), ITERATIONS);
// 应用黑帽运算(突出暗细节)
Imgproc.morphologyEx(grayMat, blackhatResult, Imgproc.MORPH_BLACKHAT, kernel,
new Point(-1, -1), ITERATIONS);
// 显示结果
showMat(mBinding.ivTophat, tophatResult);
showMat(mBinding.ivBlackhat, blackhatResult);
} catch (Exception e) {
e.printStackTrace();
Log.e("TAG", "Error in hat transforms: " + e.getMessage());
} finally {
// 释放资源
kernel.release();
tophatResult.release();
blackhatResult.release();
}
}
/**
* 演示文本增强应用
*/
private void demonstrateTextEnhancement() {
Mat kernel = Imgproc.getStructuringElement(
Imgproc.MORPH_RECT,
new Size(KERNEL_SIZE, KERNEL_SIZE)
);
Mat enhancedText = new Mat();
try {
// 使用顶帽运算增强文本(假设文本比背景暗)
// 对于暗文本 on 亮背景,使用顶帽运算可以增强文本
Imgproc.morphologyEx(grayMat, enhancedText, Imgproc.MORPH_TOPHAT, kernel);
// 增强对比度使文本更清晰
Core.multiply(enhancedText, new Scalar(2.0), enhancedText);
// 显示增强后的文本
showMat(mBinding.ivTextEnhanced, enhancedText);
} catch (Exception e) {
e.printStackTrace();
Log.e("TAG", "Error in text enhancement: " + e.getMessage());
} finally {
kernel.release();
enhancedText.release();
}
}
/**
* 演示光照校正应用
*/
private void demonstrateIlluminationCorrection() {
Mat kernel = Imgproc.getStructuringElement(
Imgproc.MORPH_RECT,
new Size(KERNEL_SIZE * 2, KERNEL_SIZE * 2) // 使用更大的核
);
Mat background = new Mat();
Mat corrected = new Mat();
try {
// 使用开运算估计背景
Imgproc.morphologyEx(grayMat, background, Imgproc.MORPH_OPEN, kernel);
// 从原图中减去背景(类似顶帽运算)
Core.subtract(grayMat, background, corrected);
// 可以显示或保存校正结果
} catch (Exception e) {
e.printStackTrace();
Log.e("TAG", "Error in illumination correction: " + e.getMessage());
} finally {
kernel.release();
background.release();
corrected.release();
}
}
/**
* 演示不同结构元素的影响
*/
private void demonstrateKernelEffects() {
// 创建不同形状的结构元素
Mat rectKernel = Imgproc.getStructuringElement(
Imgproc.MORPH_RECT, new Size(KERNEL_SIZE, KERNEL_SIZE));
Mat ellipseKernel = Imgproc.getStructuringElement(
Imgproc.MORPH_ELLIPSE, new Size(KERNEL_SIZE, KERNEL_SIZE));
Mat tophatRect = new Mat();
Mat tophatEllipse = new Mat();
try {
// 应用不同结构元素的顶帽运算
Imgproc.morphologyEx(grayMat, tophatRect, Imgproc.MORPH_TOPHAT, rectKernel);
Imgproc.morphologyEx(grayMat, tophatEllipse, Imgproc.MORPH_TOPHAT, ellipseKernel);
// 比较不同结构元素的效果
// 矩形核:产生更规则的响应
// 椭圆核:产生更平滑的响应
} finally {
// 释放资源
rectKernel.release();
ellipseKernel.release();
tophatRect.release();
tophatEllipse.release();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (originalMat != null) safeRelease(originalMat);
if (grayMat != null) safeRelease(grayMat);
}
}

在这里插入图片描述
在这里插入图片描述

posted @ 2025-09-11 15:09  yfceshi  阅读(16)  评论(0)    收藏  举报