前言:
😊😊😊欢迎来到本博客😊😊😊
🌟🌟🌟 本专栏主要结合OpenCV和C++来实现一些基本的图像处理算法并详细解释各参数含义,适用于平时学习、工作快速查询等,随时更新。
😊😊😊 具体食用方式:可以点击本专栏【OpenCV快速查找(更新中)】–>搜索你要查询的算子名称或相关知识点,或者通过这篇博客👉通俗易懂OpenCV(C++版)详细教程——OpenCV函数快速查找(不断更新中)]查阅你想知道的知识,即可食用。
🎁🎁🎁支持:如果觉得博主的文章还不错或者您用得到的话,可以悄悄关注一下博主哈,如果三连收藏支持就更好啦!这就是给予我最大的支持!😙😙😙
文章目录
- 学习目标
- 一、中值平滑原理
- 1.1 相关概念
- 1.2 原理
- 1.3 作用
- 二、C++实现
- 2.1 函数介绍
- 2.2 具体实现
- 三、 总结
学习目标
- 了解中值平滑含义
- 熟悉中值原理
- C++实现中值平滑案例
每一张图像都可能包含某种程度的噪声,噪声可以理解为由一种或者多种原因造成的灰度值的随机变化。
在大多数情况下,通过平滑技术(也常称为滤波技术)进行抑制或者去除,其中具备保持边缘(Edge Preserving)作用的平滑技术得到了更多的关注。
常用的平滑处理算法包括基于二维离散卷积的高斯平滑、均值平滑,基于统计学方法的中值平滑,具备保持边缘作用的平滑算法的双边滤波、导向滤波等。
下面将详细介绍中值平滑技术原理、常见应用及实现。
一、中值平滑原理
1.1 相关概念
中值平滑,类似于卷积,也是一种邻域运算,但计算的不是加权求和,而是对邻域中的像素点按灰度值进行排序,然后选择该组的中值作为输出的灰度值。
1.2 原理
假设输入图像为I
,高为R
、宽为C
,对于图像中的任意位置(r,c),0≤r≤R,0≤c≤C
,取以(r,c)
为中心、宽为W
、高为H
的邻域,其中W
和H
均为奇数,对邻域中的像素点灰度值进行排序,然后取中值,作为输出图像O
的(r,c)
位置处的灰度值。
例如如下图像矩阵:
取以位置(1,1)为中心的3×3邻域,对邻域中的像素点灰度值按从小到大进行排序:
可以看出141是该组灰度值的中值,那么输出图像O(1,1)=141,依此类推,会得到输出图像的所有像素点的灰度值。当然,对边缘处的处理和前几章学的卷积运算一样,可采用多种策略,而对边界进行镜像补充是较为理想的一种选择。
1.3 作用
在图像处理中,中值滤波最重要的能力是去除椒盐噪声,常用来保护边缘信息,是经典的平滑噪声的方法,该方法法对消除椒盐噪音非常有效,椒盐噪声是指在图像传输系统中由于解码误差等原因,导致图像中出现孤立的白点或者黑点。
二、C++实现
接下来介绍中值平滑的实现及其效果。
2.1 函数介绍
OpenCV并没有提供直接计算中数的函数,可以利用另外两个函数sort()
和reshape()
间接计算中数。我们先了解下这两个函数:
1、sort()
void cv::sort(InputArray src,
OutputArray dst,
int flags
)
参数 | 解释 |
---|---|
src | 输入单通道矩阵 |
dst | 输出矩阵,大小和数据类型与src一致 |
flags | 排序规则,CV_SORT_EVERY_ROW :对每行排序;CV_SORT_EVERY_COLUMN :对每列排序;CV_SORT_ASCENDING :升序排序;CV_SORT_DESCENDING :降序排序 |
2、reshape()
reshape()
函数比较有意思,它既可以改变矩阵的通道数,又可以对矩阵元素进行序列化,非常有用的一个函数。
reshape (int cn,
int rows = 0
)
这个函数参数比较少,但设置的时候却要千万小心。
参数 | 解释 |
---|---|
cn | 表示通道数(channels), 如果设为0,则表示保持通道数不变,否则则变为设置的通道数 |
rows | 表示矩阵行数。如果设为0,则表示保持原有的行数不变,否则则变为设置的行数 |
对于取邻域的中值的方法,需要利用Mat的成员函数reshape()
将矩阵变为一行或者一列,然后使用sort()
函数进行排序,最后取中间位置的数即为中数,例如:
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include <cmath>
#include <opencv2/imgproc.hpp>
using namespace std;
using namespace cv;
int main(int argc, char** argv){
Mat ImgMatrix = (Mat_<float>(3, 3) << 1, 2, 3, 4, 5, 6,7,8,9);
//将ImgMatrix转为1行,通道数不变
Mat ImgMatrix_R = ImgMatrix.reshape(1,1);
//进行排序
Mat dst;
cv::sort(ImgMatrix_R,dst,CV_SORT_EVERY_ROW);
//获取中值
float medianvalue=dst.at<float>(0, (dst.cols-1) / 2);
cout << "median value is:" << medianvalue << endl;
waitKey(0);
return 0;
}
2.2 具体实现
对于图像的中值平滑,为了省去判断边界的问题,需要对输入的图像矩阵进行扩充边界的操作。具体代码如下:
Mat medianSmooth(const Mat &Image, Size size, int borderType = BORDER_DEFAULT){
CV_Assert(Image.type() == CV_8UC1);
int H = size.height;
int W= size.width;
//窗口的高、宽均为奇数,一般设置两者是相同的
CV_Assert(H>0 && W>0);
CV_Assert(H%2==1 && W%2== 1);
//对原图像矩阵进行边界扩充
int h=(H - 1)/2;
int w=(W-1)/2;
Mat ImagePadding;
copyMakeBorder(Image, ImagePadding, h, h, w, w, borderType);
//输入图像的高、宽
int rows = Image.rows;
int cols = Image.cols;
//中值平滑后的输出图像
Mat medianI(Image.size(),CV_8UC1);
int i=0,j=0;
//中数的位置
int index=(H*W - 1)/ 2;
for (int r=h;r< h+rows; r++)
{
for (int c=w;c<w+cols; c++)
{
//取以当前位置为中心、大小为 size 的邻域
Mat region = ImagePadding(Rect(c-w,r-h, W,H)).clone();
//将该邻域转换成行矩阵
region = region.reshape(1,1);
//排序
cv::sort(region,region,CV_SORT_EVERY_ROW);
//取中数
uchar mValue = region.at<uchar>(0,index);
medianI.at<uchar>(i,j) = mValue;
j++;
}
i++;
j=0;
}
return medianI;
}
上述函数只能处理8位图,其他数据类型与之类似。利用该函数对图像进行中值平滑的主函数代码如下:
int main(int argc, char** argv) {
cv::Mat src = cv::imread("D:/VSCodeFile/OpenCV_CSDN/image/logo_gray.jpeg");
CV_Assert(!src.empty());
src.convertTo(src, CV_8UC1);
//中值滤波
Mat medianImage=medianSmooth(src,Size(5,5));
imshow("src", src);
imshow("medianImage", medianImage);
waitKey(0);
return 0;
}
一般来说,如果图像中出现较亮或者较暗的物体,若该物体大小小于中值平滑的窗口半径,那么它们基本上会被滤掉,而较大的目标则几乎会原封不动地保存下来。因此,中值平滑的窗口尺寸需要根据所遇到的不同问题而进行相应的调整。
中值平滑需要对邻域中的所有像素点按灰度值排序,一般比卷积运算要慢,有一些算法能够加速中值平滑。在OpenCV中提供了这样的函数:
void cv::medianBlur(InputArray src,
OutputArray dst,
int ksize
)
参数 | 解释 |
---|---|
src | 输入矩阵 |
dst | 输出矩阵,其大小与数据类型和src一致 |
ksize | 大于1且为奇数的核大小 |
对于上述代码,这里仅需几行即可解决:
```cpp
int main(int argc, char** argv) {
cv::Mat src = cv::imread("D:/VSCodeFile/OpenCV_CSDN/image/logo_gray.jpeg");
CV_Assert(!src.empty());
src.convertTo(src, CV_8UC1);
//中值滤波
Mat dst;
cv::medianBlur(src, dst, 5);
imshow("src", src);
imshow("dst", dst);
waitKey(0);
return 0;
}
中值平滑只是排序统计平滑中的一种,如果将取邻域的中值变为取邻域中的最小值或者最大值 ,显然会使图像变暗或者变亮。这类方法就是后面要介绍的形态学处理的基础。
高斯平滑、均值平滑在去除图像噪声时,会使图像的边缘信息变得模糊,后面将介绍在图像平滑处理过程中可以保持边缘的平滑算法:双边滤波和导向滤波。
三、 总结
最后,长话短说,大家看完就好好动手实践一下,切记不能三分钟热度、三天打鱼,两天晒网。OpenCV是学习图像处理理论知识比较好的一个途径,大家也可以自己尝试写写博客,来记录大家平时学习的进度,可以和网上众多学者一起交流、探讨,有什么问题希望大家可以积极评论交流,我也会及时更新,来督促自己学习进度。希望大家觉得不错的可以点赞、关注、收藏。