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

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

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

26. 图像腐蚀

26.1 什么是图像腐蚀

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

  • 数学定义

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

    textile 复制代码
    A ⊖ B = {z | (B)z ⊆ A}

    其中:

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

    • B 是结构元素(内核)

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

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

  • 操作过程

    腐蚀操作的工作流程:

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

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

    3. 应用规则

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

        textile 复制代码
        原始图像:
        [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]
      • 对于灰度图像:取结构元素覆盖区域内像素的最小值作为中心像素的值

        textile 复制代码
        原始图像:
        [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 关键代码分析

腐蚀核心函数:

java 复制代码
Imgproc.erode(binaryMat, erosionMat, kernel, new Point(-1, -1), ITERATIONS);

参数说明:

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

  • erosionMat: 输出图像

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

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

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

结构元素创建:

java 复制代码
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

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

  • 数学定义

    膨胀操作可以定义为:

    textile 复制代码
    A ⊕ B = {z | (B)z ∩ A ≠ ∅}

    其中:

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

    • B 是结构元素(内核)

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

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

  • 操作过程

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

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

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

    3. 应用规则

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

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

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

  • 与腐蚀操作的关系

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

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

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

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

27.2 关键代码分析

函数原型

java 复制代码
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

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 什么是开运算

定义:先腐蚀后膨胀

textile 复制代码
开运算(A) = 膨胀(腐蚀(A))
A ∘ B = (A ⊖ B) ⊕ B

作用

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

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

  • 断开细小的连接

  • 保持大体形状不变

28.2 什么是闭运算

定义:先膨胀后腐蚀

textile 复制代码
闭运算(A) = 腐蚀(膨胀(A))
A • B = (A ⊕ B) ⊖ B

作用

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

  • 连接邻近物体

  • 平滑轮廓

  • 保持大体形状不变

28.3 关键代码分析

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

java 复制代码
// 形态学操作通用函数
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

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 什么是顶帽

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

textile 复制代码
顶帽 = 原图像 - 开运算(原图像)
顶帽(A) = A - (A ∘ B)

物理意义

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

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

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

29.2 什么是黑帽

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

textile 复制代码
黑帽 = 闭运算(原图像) - 原图像
黑帽(A) = (A • B) - A

物理意义

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

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

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

29.3 关键代码分析

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

java 复制代码
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

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);
    }
}


相关推荐
notfindjob3 小时前
Opencv C++ 教程-人脸识别
c++·opencv·计算机视觉
极客智造3 小时前
OpenCV C++ 核心:Mat 与像素操作全解析
c++·人工智能·opencv
劳尔的狙击镜3 小时前
CT影像寻找皮肤轮廓预处理
python·opencv·findcontours·ct·皮肤轮廓·皮肤表皮建模·医学影像处理
极客智造3 小时前
OpenCV C++ 色彩空间详解:转换、应用与 LUT 技术
c++·人工智能·opencv
2501_916008893 小时前
uni-app iOS 日志与崩溃分析全流程 多工具协作的实战指南
android·ios·小程序·https·uni-app·iphone·webview
湫兮之风3 小时前
OpenCV: cv::warpAffine()逆仿射变换详解
人工智能·opencv·计算机视觉
文 丰3 小时前
【AndroidStudio】官网下载免安装版,AndroidStudio压缩版的配置和使用
android
WillWolf_Wang3 小时前
Linux 编译 Android 版 QGroundControl 软件并运行到手机上
android·linux·智能手机
fatiaozhang95273 小时前
数码视讯TR100-OTT-G1_国科GK6323_安卓9_广东联通原机修改-TTL烧录包-可救砖
android·xml·电视盒子·刷机固件·机顶盒刷机