opencv 四 Mat的基本操作3(高通滤波、低通滤波、对比度调节)

图像滤波分为高通滤波和低通滤波,高通滤波用于求图形的边缘,低通滤波用于图像去噪、图像模糊化等。这里的频是指变化(相邻像素值的变化),高通滤波是指使变化大也就是图像的边缘)的通过(低通滤波是指使变化小(也就是图像中图形)的通过。高通滤波部分涉及到Sobel、Scharr、Laplacian、canny等方法; 低通滤波 部分涉及: 均值滤波,方框滤波、中值滤波、高斯滤波和双边滤波。对比度调节是通过修改图像中特定区域的像素值,使图像的对比度发生变化,本博文涉及的对比度调节方法有:数值加减运算、线性变化、非线性变化、直方图均衡化等手段。

一、高通滤波

高通滤波器有Sobel、Scharr、Laplacian、canny等,这些滤波器的特点是是图像中高频部分通过,过滤掉图像中的低频部分,其核心左右就是求图形的边缘(边缘检测算子都具有方向性);且这些滤波器的值之和为0。求边缘梯度时,为了避免噪声数据的影响,可以先使用一些低通滤波器去除噪声(如:高斯滤波)。进行滤波时,结果值可能大于255(超出CV_8U的范围),所以结果数据类型通常为16S,并需要通过convertScaleAbs函数将16S的范围压缩到CV_8U。

1.1 Sobel算子和Scharr算子

是一阶导数算子,对噪音适用性很强,在内部首先使用了高斯滤波对噪音进行过滤,之后再通过一阶导求得图像边缘。【若卷积核size设置为-1,则自动使用的是Scharr滤波算法】sobel算子的细节如下图所示,左图为x方向算子(求的是垂直方向的梯度),右图为y方向算子(求的是水平方向的梯度)

Sobel滤波算子的使用代码如下,其中需要注意的是Sobel可以求水平方向梯度垂直方向梯度,所求的梯度值可能为负数,因此需要使用convertScaleAbs取绝对值。最终的梯度结果需要使用addWeighted函数进行加权求和

#include<iostream>  
#include <opencv2/core/core.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/imgproc/imgproc.hpp>
 
using namespace std;
using namespace cv;
 
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
 
int main()
{
    // 读入一张图片  
    Mat image = imread("C:\\Users\\aaa\\Pictures\\car1.png");
    //彩色图像转灰度图像
    cvtColor(image, image, COLOR_BGR2GRAY);
    imshow("原图", image);
     
    Mat grad_x, grad_y;
    Mat abs_grad_x, abs_grad_y;
     
    //求 X方向梯度,竖直边缘
    Sobel(image, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT);
    //求 Y方向梯度,水平边缘
    Sobel(image, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT);
    //将中间结果转换到 CV_8U:
    convertScaleAbs(grad_x, abs_grad_x);
    imshow("x", abs_grad_x);
    convertScaleAbs(grad_y, abs_grad_y);
    imshow("y", abs_grad_y);
    //将两个方向的梯度相加来求取近似 
    addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, image);
 
    imshow("sobel", image);
 
     
    waitKey(0);
}
 

 代码运行效果如下所示: 

虽然Sobel算子可以有效的提取图像边缘,但是对图像中较弱的边缘提取效果较差。因此为了能够有效的提取出较弱的边缘,需要将像素值间的差距增大,因此引入Scharr算子,其结构如下图所示。

 Scharr算子是对Sobel算子差异性的增强,因此两者之间的在检测图像边缘的原理和使用方式上相同。Scharr算子的边缘检测滤波的尺寸为3×3,因此也有称其为Scharr滤波器。可以通过将滤波器中的权重系数放大来增大像素值间的差异。参考自【OpenCV 4开发详解】Scharr算子 - 知乎

1.2 Laplacian算子

  拉普拉斯算子(Laplacian)可应用到图像边缘检测中。在OpenCV中当kernel大小为3*3时,支持两种kernel算子,分别如下图所示。Laplacian算子可以同时求水平方向和垂直方向的的梯度,具有无方向性的特点Laplacian算子是一种二阶导数算子,对噪声比较敏感,因此常需要配合高斯滤波(去除噪声)一起使用。

调用代码如下所示:

  Mat image = imread("C:\\Users\\aaa\\Pictures\\car1.png");
    //彩色图像转灰度图像
    cvtColor(image, image, COLOR_BGR2GRAY);
    imshow("原图", image);
     
    Mat grad;
    Laplacian(image, grad, CV_8U);
    convertScaleAbs(grad, grad);
    imshow("Laplacian", grad);
     
    waitKey(0);

代码结果如下所示 

 1.3 cany算子

cany算子分四步实现:消除噪声=》计算幅值和方向=》非极大值抑制=》滞后阈值,其计算幅值和方向时使用了sobel算子计算x和y方向的梯度值。具体细节如下博客所示OpenCV之Canny边缘检测(C++实现)https://blog.csdn.net/xddwz/article/details/111585648

 如上述的三种滤波器相比,cany的计算流程较为复杂,且运算量较大。其使用代码和运行结果如下所示。

   // 读入一张图片  
    Mat image = imread("C:\\Users\\aaa\\Pictures\\car1.png");
    //彩色图像转灰度图像
    cvtColor(image, image, COLOR_BGR2GRAY);
    imshow("原图", image);
     
    Mat grad;
/*
void Canny( InputArray image, OutputArray edges,
                         double threshold1, double threshold2,
                         int apertureSize = 3, bool L2gradient = false )
*/
    Canny(image, grad, 150, 100, 3);
    imshow("grad", grad);
     
    waitKey(0);

二、低通滤波

OpenCV中常用的低通滤波有: 均值滤波,方框滤波、中值滤波、高斯滤波和双边滤波,主要用于去除图像中的噪声。

2.1 均值滤波和方框滤波

均值滤波 可以帮助消除图像尖锐噪声,实现图像平滑,模糊等功能。理想的均值滤波是用每个像素和它周围像素计算出来的平均值替换图像中每个像素。

均值滤波有平均均值滤波和加权均值滤波。分别如下所示:

      左边是平均均值滤波                                                       右边是加权均值滤波 

 方框滤波和均值滤波的原理是类似的,方框滤波在进行归一化后效果和相同卷积核大小的均值滤波的效果相同。在OpenCV中,方框滤波的核大小就是指方框的行数和列数,具体如下图所示:

 调用代码如下所示:

#include <iostream>
#include <opencv2\opencv.hpp>

using namespace cv;
using namespace std;

int main()
{
	//---------------------------用于方框滤波的图像--------------------
	Mat img = imread("C:\\Users\\aaa\\Pictures\\timg.jpg",0);
	resize(img, img, { 500,300 });

	//将CV_8U类型转换成CV_32F类型,避免计算后的数据过大
	Mat equalImg_32F;
	img.convertTo(equalImg_32F, CV_32F, 1.0 / 255);
	Mat resultNorm, result, equalImg_32FSqr;
	//--------------------------方框滤波boxFilter----------------------
	boxFilter(img, resultNorm, -1, Size(3, 3), Point(-1, -1), true);  // 进行归一化,则为均值滤波
	boxFilter(img, result, -1, Size(3, 3), Point(-1, -1), false);     // 不进行归一化
	//----------------------方框滤波sqrBoxFilter()---------------------
	//对每个像素数值的平方求和/求均值
	sqrBoxFilter(equalImg_32F, equalImg_32FSqr, -1, Size(3, 3), Point(-1, -1), true, BORDER_CONSTANT);
	//-------------------------显示处理结果----------------------------
	imshow("原始图像", img);
	imshow("归一化", resultNorm);
	imshow("不归一化", result);
	imshow("平方和求均值", equalImg_32FSqr);
	waitKey(0);
	return 0;
}

代码运行效果如下·所示 

 2.2 中值滤波 

中值滤波在一定的条件下可以克服常见线性滤波器如方框滤波器、均值滤波等带来的图像细节模糊,而且对滤除脉冲干扰及图像扫描噪声非常有效,也常用于保护边缘信息, 保存边缘的特性使它在不希望出现边缘模糊的场合也很有用,是非常经典的平滑噪声处理方法。以下内容参考自:Opencv之图像滤波:5.中值滤波(cv2.medianBlur)_Justth.的博客-CSDN博客_opencv 中值滤波之前介绍的均值滤波、方框滤波、高斯滤波,都是线性滤波方式。由于线性滤波的结果是所有像素值的线性组合,因此含有噪声的像素也会被考虑进去,噪声不会被消除,而是以更柔和的方式存在。这时使用非线性滤波效果可能会更好。中值滤波与前面介绍的滤波方式不同,不再采用加权求均值的方式计算滤波结果。它用邻域内所有像素值的中间值来替代当前像素点的像素值。5.1 原理介绍中值滤波会取当前像素点及其周围临近像素点(一共有奇数个像素点)的像素值,将这些像素值排序,然后将位于中间位置的像素值作为当前像素点的像素值。 对如下矩..https://blog.csdn.net/qq_49478668/article/details/123485382

 中值滤波会取当前像素点及其周围临近像素点(一共有奇数个像素点)的像素值,将这些像素值排序,然后将位于中间位置的像素值作为当前像素点的像素值。 对如下矩阵:

 将其邻域设置为3×3大小,对其3×3邻域内像素点的像素值进行排序(升序降序均可),按升序排序后得到序列值为:[66,78,90,91,93,94,95,97,101]。在该序列中,处于中心位置(也叫中心点或中值点)的值是“93”,因此用该值替换原来的像素值78,作为当前点的新像素值。中值滤波效果如下:

 调用代码如下所示:

#include <iostream>
#include <opencv2\opencv.hpp>

using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("C:\\Users\\aaa\\Pictures\\timg.jpg",0);
	resize(img, img, { 500,300 });
	Mat grayResult;
	medianBlur(img, grayResult , 3);
	imshow("原始图像", img);
	imshow("中值滤波", grayResult);
	waitKey(0);
	return 0;
}

代码运行效果如下: 

 2.3 高斯滤波 

高斯滤波用于图像平衡、图像模糊或图像去噪,其本身是一个加权滤波的实现形式,高斯滤波的算子是有高斯分布函数计算而来的(中间的最重要,外围的次之)【高斯分布有标准差和方差两个参数】。高斯滤波的算子如下所示,其为每一个位置分配了不同权重,中间的权重最大,边缘的次之,所有的权重之和为1。

使用高斯滤波的代码和效果如下所示: 

    Mat img = imread("C:\\Users\\aaa\\Pictures\\timg.jpg",0);
	resize(img, img, { 500,300 });
	Mat out;
	GaussianBlur(img, out, cv::Size(5, 5), 3, 3);
	imshow("原始图像", img);
	imshow("out", out);
	waitKey(0);

2.4 双边滤波

双边滤波(Bilateral filter)是一种非线性的滤波方法,双边滤波器的好处是可以做边缘保存(edge preserving)。一般用高斯滤波去降噪,会较明显地模糊边缘,对于高频细节的保护效果并不明显,而使用双边滤波可以达到高斯滤波的降噪效果,且能很好的保留边缘(因为进行双边滤波器时比高斯滤波多了一个高斯方差sigma-d)。内容参考自:OpenCV学习笔记(七)中值、双边滤波 - 简书

使用双边滤波的代码和效果如下所示: 

Mat img = imread("C:\\Users\\aaa\\Pictures\\timg.jpg",0);
	resize(img, img, { 500,300 });
	Mat out;
	/*
	bilateralFilter( InputArray src, OutputArray dst, int d,
                                   double sigmaColor, double sigmaSpace,
                                   int borderType = BORDER_DEFAULT )
	*/
	bilateralFilter(img, out, 25, 25 * 2, 25 / 2);
	imshow("原始图像", img);
	imshow("out", out);
	waitKey(0);

三、对比度调节

对比度调节是通过修改图像中特定区域的像素值,使图像的对比度发生变化。对比度增强是使亮部更亮、暗部更暗;对比度增强减弱是指使亮部变暗、暗部变亮。具体的对比度调节方法有:数值加减运算(无法增强对比度,只能使图像整体变亮或变暗)、线性变化(将像素值进行线性函数变化,y=ax+b,a>1时增强对比度,a<1时削弱对比度)、非线性变化(对数变化、gramma变化、自定义变化函数)、直方图均衡化等手段。

3.1 数值加减运算

通过对mat加减法可以调节图像的亮度(无法调节对比度),当加法运算结果超过255时为255,减法运算结果小于0时为0。

测试代码如下

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include<opencv2/core/core.hpp>
#include<iostream>
 
using namespace std;
using namespace cv;
int main()
{
	Mat srcImage;
	srcImage = imread("C:/Users/aaa/Pictures/b.jpg", 0);
	resize(srcImage, srcImage,{},0.5,0.5);
	imshow("img", srcImage);
	Mat img_bl,img_an;
	//
	img_an = srcImage - 50;//减=》变暗
	img_bl = srcImage + 50;//加=》变亮
	imshow("img-50", img_an);
	imshow("img+50", img_bl);
	waitKey(0);
	return 0;
}

 代码执行效果如下所示 

3.2 线性运算

通过y=ax+b的线性函数映射修改像素值,a>1即增强对比度,a<1即降低对比度

测试代码如下

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include<opencv2/core/core.hpp>
#include<iostream>
 
using namespace std;
using namespace cv;
int main()
{
	Mat srcImage;
	srcImage = imread("C:/Users/aaa/Pictures/b.jpg", 0);
	resize(srcImage, srcImage,{},0.5,0.5);
	imshow("img", srcImage);
	Mat img_bl,img_an;
	//
	img_an = srcImage*0.8 + 10;//a<1=》变暗
	img_bl = srcImage*1.5 - 30;//a>1=》变亮
	imshow("img*0.6 + 10", img_an);
	imshow("img*1.2 - 30", img_bl);
	waitKey(0);
	return 0;
}

代码执行效果如下所示 

3.3 非线性变换

opencv中常见的非线性变换为对数变换(类凸函数,在y=x函数图像上方)和伽马变换 (随gamma值变化可在y=x函数图像下方,也可在其上方),此外我们还可以自定义非线性函数实现图像亮度的调节。

进行对数变化、gamma 的代码如下所示。进行对数变化时需要注意的是,对数变化对应的值域是0~255,但是进行对数变化后输出的值域却超过了255,故需要使用normalize函数进行值域压缩到0~255。而对于flaot型的mat,opencv理解的范围为[0,1],既超过1的部分均为白色,故最后需要将CV_32F转换为CV_8U(或使用normalize函数进行值域压缩到[0,1])

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include<opencv2/core/core.hpp>
#include<iostream>
 
using namespace std;
using namespace cv;
 
int main()
{
	Mat srcImage;
	srcImage = imread("C:/Users/aaa/Pictures/b.jpg", 0);
	resize(srcImage, srcImage,{},0.5,0.5);
	imshow("img", srcImage);
	
	//log变化、对数变化,使图像变亮
	Mat img_log;
	srcImage.convertTo(srcImage, CV_32F);
	log(srcImage, img_log);//log变化后图像值会出现小数,log函数的输出范围超出0~255
	normalize(img_log, img_log, 0, 255, NORM_MINMAX);//将log函数的输出压缩到0~255内
	img_log.convertTo(img_log, CV_8U);//因为图像是CV_32F,且值域超出0~1
 
	//gamma变化
	Mat g2_05=gamma_mat(srcImage, 2, 0.5);
	Mat g1_13 = gamma_mat(srcImage, 2, 1.3);
	imshow("img_log", img_log);
	imshow("g2_05", g2_05);
	imshow("g1_13", g1_13);
	waitKey(0);
	return 0;
}

其中gamma_mat函数的实现如下,需要注意的是 gamma函数变化针对的是0,1之间的值域,所以要将mat(值域为0~255)的像素值进行归一化(也就是现将CV_8U转换为CV_64F,然后除以255)。其运算结果也不在0,1之间,故需要进行也归一化(或者归255化)。

Mat gamma_mat(Mat src,float c, float gamma) {
	//伽马变换
	Mat gamma_image;  //归一化图像
	src.convertTo(src, CV_64F);
	src = src / 255;
	int height = src.rows;
	int width = src.cols;
	gamma_image.create(src.size(), CV_64F);
	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			gamma_image.at<double>(i, j) = c*pow(src.at<double>(i, j), gamma);
 
		};
	};
    //方式一  归一化
    normalize(gamma_image, gamma_image, 0, 1, NORM_MINMAX);
    //方式二  归255 转CV_8U
	//normalize(gamma_image, gamma_image, 0, 255, NORM_MINMAX);
	//gamma_image.convertTo(gamma_image, CV_8U);
    //方式三  归255 转CV_8U
	//gamma_image.convertTo(gamma_image, CV_8U, 255, 0);
	return gamma_image;
}

3.4 直方图均衡化

使用直方图均衡化可以增强图像对比度,其详细概念可以参考以下链接:
OpenCV 直方图均衡化_杨 戬的博客-CSDN博客_opencv直方图均衡化文章目录直方图均衡化介绍图像的直方图是什么?更形象解释什么是直方图均衡化?直方图均衡化是如何实现的?直方图均衡化的作用直方图均衡化步骤相关APIequalizeHist代码示例直方图均衡化介绍图像的直方图是什么?图像直方图,是指对整个图像像在灰度范围内的像素值(0~255)统计出现频率次数,据此生成的直方图,称为图像直方图-直方图。直方图反映了图像灰度的分布情况。是图像的统计学特征。简单来说:直方图是图像中像素强度分布的图形表达方式,它统计了每一个强度值所具有的像素个数。例如下面这张图片,左图为灰_1671465600https://blog.csdn.net/weixin_45525272/article/details/122663041

灰度图 直方图均衡化直接调用equalizeHist(in_mat,out_mat)即可,具体调用案例如下所示 

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include<opencv2/core/core.hpp>
#include<iostream>
 
using namespace std;
using namespace cv;
 
int main()
{
	Mat srcImage;
	srcImage = imread("C:/Users/aaa/Pictures/a.jpg", 0);
	resize(srcImage, srcImage,{},0.5,0.5);
	imshow("原始图", srcImage);
	Mat new_img;
	equalizeHist(srcImage, new_img);
	imshow("经过直方图均衡化后的图", new_img);
	waitKey(0);
	return 0;
}

代码执行效果如下所示 

彩色图 直方图均衡化不能直接对每一个通道进行均衡化,尤其是bgr图像。对于这种bgr图像需要先将颜色空间从BGR转换为HSV,然后将图像分割三个mat(分布对象H、S、V三个通道),对V通道的mat进行直方图均衡化即可。最后,要将H、S、V三个通道单通道mat合并为一个三通道的mat,然后将hsv颜色空间的mat转换为bgr的mat。具体操作如下所示

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include<opencv2/core/core.hpp>
#include<iostream>
 
using namespace std;
using namespace cv;
Mat brgEqualizeHist(Mat bgr) {
	Mat hsvImg, dstImage;
	cvtColor(bgr, hsvImg, COLOR_BGR2HSV);
	//定义一个channels储存分离后的通道
	vector<Mat> channels;
	//H,S,V三个通道
	Mat h, s, v;
	//分离图像
	split(hsvImg, channels);
 
	h = channels.at(0);
	s = channels.at(1);
	v = channels.at(2);
	//对V通道就行直方图均衡化,使图像的亮度变得均衡
	equalizeHist(v, v);
 
	//将直方图均衡化的通道合并起来
	merge(channels, hsvImg);
	cvtColor(hsvImg, dstImage, COLOR_HSV2BGR);
	return dstImage;
}
int main()
{
	Mat srcImage;
	srcImage = imread("C:/Users/aaa/Pictures/a.jpg", 1);
	resize(srcImage, srcImage,{},0.5,0.5);
	imshow("原始图", srcImage);
	Mat new_bgr = brgEqualizeHist(srcImage);
	imshow("经过直方图均衡化后的图", new_bgr);
	waitKey(0);
	return 0;
}

 代码执行效果如下所示

猜你喜欢

转载自blog.csdn.net/m0_74259636/article/details/128587670