首页 > 编程语言 >C++之OpenCV入门到提高003:矩阵的掩膜(Mask)处理

C++之OpenCV入门到提高003:矩阵的掩膜(Mask)处理

时间:2024-11-01 09:58:01浏览次数:4  
标签:src 掩膜 int Mask C++ 图像 col row

一、介绍
    今天是这个系列《C++之 Opencv 入门到提高》得第三篇文章。今天这篇文章也不难,主要介绍如何使用 Opencv 对图像进行掩膜处理,提高图像的对比度。在这个过程中,我们可以学到如何获取图像指针、如何处理像素值越界等问题。我们一步一个脚印的走,收获就会越来越多。虽然操作很简单,但是要下功夫理解每个技术点,把基础打扎实,才能让以后得学习过程更顺利一点。OpenCV 具体的简介内容,我就不多说了,网上很多,大家可以自行脑补。
    OpenCV 的官网地址:https://opencv.org/,组件下载地址:https://opencv.org/releases/
    OpenCV 官网学习网站:https://docs.opencv.ac.cn/4.10.0/index.html

    我需要进行说明,以防大家不清楚,具体情况我已经罗列出来。
        操作系统:Windows Professional 10(64位)
        开发组件:OpenCV – 4.10.0
        开发工具:Microsoft Visual Studio Community 2022 (64 位) - Current版本 17.8.3
        开发语言:C++(VC16)

二、实例学习
    这一节的内容不多,接口 API 其实也是挺简单的。但是我们学习接口,不能冷冰冰的只是学API、一些基础知识也是要掌握的。
    图像中的掩膜(Mask)是什么?在图像处理中,掩膜(Mask)是一种用于控制图像处理区域或处理过程的特殊图像。它通常是一个与原始图像同样大小的二维矩阵,用于选择性地遮盖或显示图像的特定区域。掩膜可以用于多种图像处理任务,如图像分割、特征提取、增强等。
    在数字图像处理中,掩膜通常是一个二进制图像,其中像素值为1的区域表示要保留的区域,像素值为0的区域表示要排除的区域。通过将掩膜与原始图像进行逻辑运算,可以创建新的图像,其中只有掩膜中标记为1的区域被保留,其他区域被排除。
    掩膜在图像处理中有多种应用。例如,在图像分割中,掩膜可用于选择性地突出显示感兴趣的区域,以便进一步处理或分析。在特征提取中,掩膜可用于提取图像中的特定形状或结构。此外,掩膜还可以用于图像增强,例如通过模糊或锐化特定区域来改善图像质量。

    数字图像处理中,掩模为二维矩阵数组,有时也用多值图像,图像掩模主要用于:
      ①提取感兴趣区,用预先制作的感兴趣区掩模与待处理图像相乘,得到感兴趣区图像,感兴趣区内图像值保持不变,而区外图像值都为0。
      ②屏蔽作用,用掩模对图像上某些区域作屏蔽,使其不参加处理或不参加处理参数的计算,或仅对屏蔽区作处理或统计。
      ③结构特征提取,用相似性变量或图像匹配方法检测和提取图像中与掩模相似的结构特征。
      ④特殊形状图像的制作。

    什么是位图与掩膜的与运算?
      其实就是原图中的每个像素和掩膜中的每个对应像素进行与运算。比如1 & 1 = 1;1 & 0 = 0;
      比如一个3 * 3的图像与3 * 3的掩膜进行运算,得到的结果图像就是:
    
      说白了,我们使用掩膜(mask)位图来选择哪个像素允许拷贝,哪个像素不允许拷贝。如果mask像素的值是非0的,我就拷贝它,也就是保留下,否则不拷贝,不保留。

    在所有图像基本运算的操作函数中,凡是带有掩膜(mask)的处理函数,其掩膜都参与运算(输入图像运算完之后再与掩膜图像或矩阵运算)。

 1 #include <opencv2/opencv.hpp>
 2 #include <iostream>
 3 #include <math.h>
 4 
 5 using namespace std;
 6 using namespace cv;
 7 
 8 
 9 int main()
10 {
11     Mat src, dst;
12     src = imread("D:\\360MoveData\\Users\\Administrator\\Desktop\\TestImage\\4.jpg", IMREAD_UNCHANGED);
13     if (!src.data)
14     {
15         cout << "图片加载错误!!!" << endl;
16         return -1;
17     }
18 
19     namedWindow("原始图像", WINDOW_AUTOSIZE);
20     imshow("原始图像", src);
21 
22     //1、获取图形像素指针
23     //CV_Assert(myImage.depth()==CV_8U);
24     //Mat.ptr<uchar>(int i=0)获取像素矩阵的指针,索引 i 表示第几行,从0开始计数。
25     //获取当前行指针 const uchar* current=myImage.ptr<uchar>(row);
26     //获取当前像素点 p(row,col) 的像素值 p(row,col)=current[col];
27 
28     //2、像素范围处理 saturate_cast<uchar>
29     //saturate_cast<uchar>(-100),返回 0.
30     //saturate_cast<uchar>(288),返回 255,
31     //saturate_cast<uchar>(100),返回 100.
32     // 这个函数的功能是确保 RGB 值的范围在 0-255 之间。
33 
34     double startDate = getTickCount();
35 
36     //第一种实现对比度
37     /*int cols = (src.cols - 1) * src.channels();
38     int offerts = src.channels();
39     int rows = src.rows;
40 
41     dst = Mat(src.size(), src.type());
42 
43     for (int row = 1; row < (rows - 1); row++)
44     {
45         const uchar* previous = src.ptr<uchar>(row - 1);
46         const uchar* current = src.ptr<uchar>(row);
47         const uchar* next = src.ptr<uchar>(row + 1);
48         uchar* output = dst.ptr<uchar>(row);
49         for (int col = offerts; col < cols; col++)
50         {
51             output[col] = saturate_cast<uchar>(5 * current[col] - (current[col - offerts] + current[col + offerts] + previous[col] + next[col]));
52         }
53     }*/
54 
55     //3、定义掩膜
56     // Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
57     //filter2D(src, dst, src.depth(), kernel):src 是原图,dst 是目标图,src.depth() 表示位图深度,有 32,24,8 等。
58 
59     //第二种实现对比度
60     Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
61     filter2D(src, dst, src.depth(), kernel);
62 
63     double totalTime = (getTickCount() - startDate) / getTickFrequency();
64 
65     cout << "消费时间:"<< totalTime << endl;
66 
67     namedWindow("对比度图像", WINDOW_AUTOSIZE);
68     imshow("对比度图像", dst);
69 
70 
71     waitKey(0);
72 
73     return 0;
74 }

    生成的效果图如下:

    

    当然了,在源码中,提供了两种实现,效果都是一样的。一种是自己实现的,一种是通过调用接口实现的。这也说明了一个问题,只要你掌握的够深入,和接口效果一样的问题,你也可以写得出。

    如果我们吧注释的代码打开,把【第二种实现】注释掉,源码:

 1 int cols = (src.cols - 1) * src.channels();
 2 int offerts = src.channels();
 3 int rows = src.rows;
 4 
 5 dst = Mat(src.size(), src.type());
 6 
 7 for (int row = 1; row < (rows - 1); row++)
 8 {
 9     const uchar* previous = src.ptr<uchar>(row - 1);
10     const uchar* current = src.ptr<uchar>(row);
11     const uchar* next = src.ptr<uchar>(row + 1);
12     uchar* output = dst.ptr<uchar>(row);
13     for (int col = offerts; col < cols; col++)
14     {
15         output[col] = saturate_cast<uchar>(5 * current[col] - (current[col - offerts] + current[col + offerts] + previous[col] + next[col]));
16     }
17 }

    而且,自己写的性能更好。对比如图:

    

    再看看我们自己写的运行时间:

    

    我们自己写实现的效果:

    


    
三、总结
    这是 C++ 使用 OpenCV 的第三篇文章,概念挺难懂的,其实操作起来也没那么难,当然了,这只是我们入门的开始,那就继续吧。皇天不负有心人,不忘初心,继续努力,做自己喜欢做的,开心就好。

标签:src,掩膜,int,Mask,C++,图像,col,row
From: https://www.cnblogs.com/PatrickLiu/p/18512814

相关文章

  • PCL 法线微分(DoN)分割(C++详细过程版)
    目录一、概述二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接,首发于:2024年11月1日。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的抄袭狗。一、概述  法线微分(DoN)分割在PCL里有现成的调用函数,具体算法原理和实现代码见:PCL基于法线微分(D......
  • C++《list》
    在本篇当中我们将学习STL中的list,在此list就是我们之前在数据结构学习过的链表,在本篇中我们要来了解list当中的成员函数该如何使用,由于list各个函数的接口和之前学习过的vector类型,因此在学习list的使用就较为轻松。在lis篇章中我们要重点了解的是在下一个篇章当中的list模拟实......
  • C++ 手撕--基本数据结构的简单实现
    C++面试手撕代码----基本数据结构的简单实现1.String数据结构的简单实现:#include<iostream>#include<cstring>//forstrcpystrlenmethodsusingnamespacestd;classString{private: char*data; size_tlength;public: String():data(nullptr),length(0)......
  • 华为OD机试-(E卷,100分) - 补种未成活胡杨(Java & Python& JS & C++ & C )
    最新华为OD机试题目描述近些年来,我国防沙治沙取得显著成果。某沙漠新种植N棵胡杨(编号1-N),排成一排。一个月后,有M棵胡杨未能成活。现可补种胡杨K棵,请问如何补种(只能补种,不能新种),可以得到最多的连续胡杨树?输入描述N总种植数量,1<=N<=100000M未成活胡杨数量,M个空格......
  • C++对象优化4条原则
    1、函数参数传递优先使用传引用,而不是传值①、函数参数传递的过程是赋值的过程,对象之间赋值是会产生赋值运算符的重载调用,退出函数时还会再调用一次析构函数,传引用就不存在上述函数调用2、函数返回一个临时对象时,应该直接返回,而不应该先定义一个临时对象,然后返回定义的临......
  • C++笔记---可变参数模板
    1.简单介绍与基本语法可变参数模板是指模板的类型参数列表的的参数个数可变。C++11支持可变参数模板,也就是说支持可变数量参数的函数模板和类模板,可变数目的参数被称为参数包,存在两种参数包:模板参数包:表示零或多个模板参数。函数参数包:表示零或多个函数参数。参数包的......
  • 深入计算机语言之C++:内存管理
    ......
  • C++三五法则
     若类中有资源在构造函数中创建,并在析构函数中释放,此时需要显式定义拷贝构造、赋值,析构等操作,若在程序没有显示声明并定义时,会被隐式生成,对于不包含联合体的类,隐式生成的拷贝构造函数和赋值运算在执行时,会按成员对象依次复制,隐式生成的析构函数为空如下面的类T管理资源int*clas......
  • C++泛型一:模板
    数据类型给程序设计带来的困扰及解决方案intmaxt(int,int);doublemaxt(double,double);若有一种占位符T,能够代替类型,便可以简化代码的冗余编写Tmaxt(T,T);C++模板模板声明如下template<typenameT1,...>template是C++的模板声明关键字,尖括号内为模板参数列表typ......
  • C++范型二:右值引用
    为类所设计的转移语义拷贝构造函数和转移语义赋值运算符使得临时对象有了将资源直接转移给另一个对象的能力,从而避免了内存分配、资源拷贝等深拷贝过程作为注重效率的模板,当然要引入右值引用及相关技术,其成果就是参数完美转发模板右值引用左值和右值左值代表一块存储空间,可以......