返回:OpenCV系列文章目录(持续更新中......)
上一篇:使用OpenCV4.9的随机生成器和文本
下一篇:OpenCV系列文章目录(持续更新中......)
目标
在本教程中,您将学习如何使用 OpenCV 函数将不同的线性滤波器应用于平滑图像,例如:
- blur()
- GaussianBlur()
- medianBlur()
- bilateralFilter()
相关理论
注意
下面的解释属于 Richard Szeliski 的《计算机视觉:算法和应用》一书和 LearningOpenCV
- 平滑,也称为模糊,是一种简单且常用的图像处理操作。
- 平滑的原因有很多。在本教程中,我们将重点介绍平滑处理以减少噪声(其他用途将在以下教程中看到)。
-
它有助于将滤镜可视化为在图像上滑动的系数窗口。
- 过滤器有很多种,这里我们提一下最常用的:
归一化盒过滤器
- 这个过滤器是最简单的!每个输出像素都是其内核邻居的平均值(所有像素的权重相等)
- 内核如下:
高斯滤波器
- 可能是最有用的过滤器(虽然不是最快的)。高斯滤波是通过用高斯核对输入数组中的每个点进行卷积,然后将它们相加以生成输出数组来完成的。
- 为了让图片更清晰,还记得一维高斯核的样子吗?
假设图像是 1D 的,您可以注意到位于中间的像素将具有最大的权重。其相邻像素的权重随着它们与中心像素之间的空间距离的增加而减小。
注意
请记住,二维高斯可以表示为:
中值滤波器
中值滤波器贯穿信号的每个元素(在本例中为图像),并将每个像素替换为其相邻像素的中位数(位于评估像素周围的方形邻域中)。
双边过滤器
- 到目前为止,我们已经解释了一些过滤器,其主要目标是平滑输入图像。然而,有时滤波器不仅可以溶解噪声,还可以消除边缘。为了避免这种情况(至少在一定程度上),我们可以使用双边过滤器。
- 与高斯滤波器类似,双边滤波器也会考虑相邻像素,并为每个像素分配权重。这些权重有两个组成部分,第一个分量与高斯滤波器使用的权重相同。第二个组件考虑了相邻像素和评估像素之间的强度差异。
- 有关更详细的说明,您可以查看此链接
代码:
- 这个程序是做什么的?
- 加载图像
- 应用 4 种不同类型的滤镜(在理论中解释)并按顺序显示滤波后的图像
- 可下载代码: 点击这里
- 代码一览:
C++:
#include <iostream>
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"using namespace std;
using namespace cv;int DELAY_CAPTION = 1500;
int DELAY_BLUR = 100;
int MAX_KERNEL_LENGTH = 31;Mat src; Mat dst;
char window_name[] = "Smoothing Demo";int display_caption( const char* caption );
int display_dst( int delay );int main( int argc, char ** argv )
{namedWindow( window_name, WINDOW_AUTOSIZE );const char* filename = argc >=2 ? argv[1] : "lena.jpg";src = imread( samples::findFile( filename ), IMREAD_COLOR );if (src.empty()){printf(" Error opening image\n");printf(" Usage:\n %s [image_name-- default lena.jpg] \n", argv[0]);return EXIT_FAILURE;}if( display_caption( "Original Image" ) != 0 ){return 0;}dst = src.clone();if( display_dst( DELAY_CAPTION ) != 0 ){return 0;}if( display_caption( "Homogeneous Blur" ) != 0 ){return 0;}for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){blur( src, dst, Size( i, i ), Point(-1,-1) );if( display_dst( DELAY_BLUR ) != 0 ){return 0;}}if( display_caption( "Gaussian Blur" ) != 0 ){return 0;}for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){GaussianBlur( src, dst, Size( i, i ), 0, 0 );if( display_dst( DELAY_BLUR ) != 0 ){return 0;}}if( display_caption( "Median Blur" ) != 0 ){return 0;}for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){medianBlur ( src, dst, i );if( display_dst( DELAY_BLUR ) != 0 ){return 0;}}if( display_caption( "Bilateral Blur" ) != 0 ){return 0;}for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){bilateralFilter ( src, dst, i, i*2, i/2 );if( display_dst( DELAY_BLUR ) != 0 ){return 0;}}display_caption( "Done!" );return 0;
}int display_caption( const char* caption )
{dst = Mat::zeros( src.size(), src.type() );putText( dst, caption,Point( src.cols/4, src.rows/2),FONT_HERSHEY_COMPLEX, 1, Scalar(255, 255, 255) );return display_dst(DELAY_CAPTION);
}int display_dst( int delay )
{imshow( window_name, dst );int c = waitKey ( delay );if( c >= 0 ) { return -1; }return 0;
}
Java:
import org.opencv.core.*;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;class SmoothingRun {int DELAY_CAPTION = 1500;int DELAY_BLUR = 100;int MAX_KERNEL_LENGTH = 31;Mat src = new Mat(), dst = new Mat();String windowName = "Filter Demo 1";public void run(String[] args) {String filename = ((args.length > 0) ? args[0] : "../data/lena.jpg");src = Imgcodecs.imread(filename, Imgcodecs.IMREAD_COLOR);if( src.empty() ) {System.out.println("Error opening image");System.out.println("Usage: ./Smoothing [image_name -- default ../data/lena.jpg] \n");System.exit(-1);}if( displayCaption( "Original Image" ) != 0 ) { System.exit(0); }dst = src.clone();if( displayDst( DELAY_CAPTION ) != 0 ) { System.exit(0); }if( displayCaption( "Homogeneous Blur" ) != 0 ) { System.exit(0); }for (int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2) {Imgproc.blur(src, dst, new Size(i, i), new Point(-1, -1));displayDst(DELAY_BLUR);}if( displayCaption( "Gaussian Blur" ) != 0 ) { System.exit(0); }for (int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2) {Imgproc.GaussianBlur(src, dst, new Size(i, i), 0, 0);displayDst(DELAY_BLUR);}if( displayCaption( "Median Blur" ) != 0 ) { System.exit(0); }for (int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2) {Imgproc.medianBlur(src, dst, i);displayDst(DELAY_BLUR);}if( displayCaption( "Bilateral Blur" ) != 0 ) { System.exit(0); }for (int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2) {Imgproc.bilateralFilter(src, dst, i, i * 2, i / 2);displayDst(DELAY_BLUR);}displayCaption( "Done!" );System.exit(0);}int displayCaption(String caption) {dst = Mat.zeros(src.size(), src.type());Imgproc.putText(dst, caption,new Point(src.cols() / 4, src.rows() / 2),Imgproc.FONT_HERSHEY_COMPLEX, 1, new Scalar(255, 255, 255));return displayDst(DELAY_CAPTION);}int displayDst(int delay) {HighGui.imshow( windowName, dst );int c = HighGui.waitKey( delay );if (c >= 0) { return -1; }return 0;}
}public class Smoothing {public static void main(String[] args) {// Load the native library.System.loadLibrary(Core.NATIVE_LIBRARY_NAME);new SmoothingRun().run(args);}
}
Python:
import sys
import cv2 as cv
import numpy as np# Global VariablesDELAY_CAPTION = 1500
DELAY_BLUR = 100
MAX_KERNEL_LENGTH = 31src = None
dst = None
window_name = 'Smoothing Demo' def main(argv):cv.namedWindow(window_name, cv.WINDOW_AUTOSIZE)# Load the source imageimageName = argv[0] if len(argv) > 0 else 'lena.jpg'global srcsrc = cv.imread(cv.samples.findFile(imageName))if src is None:print ('Error opening image')print ('Usage: smoothing.py [image_name -- default ../data/lena.jpg] \n')return -1if display_caption('Original Image') != 0:return 0global dstdst = np.copy(src)if display_dst(DELAY_CAPTION) != 0:return 0# Applying Homogeneous blurif display_caption('Homogeneous Blur') != 0:return 0 for i in range(1, MAX_KERNEL_LENGTH, 2):dst = cv.blur(src, (i, i))if display_dst(DELAY_BLUR) != 0:return 0 # Applying Gaussian blurif display_caption('Gaussian Blur') != 0:return 0 for i in range(1, MAX_KERNEL_LENGTH, 2):dst = cv.GaussianBlur(src, (i, i), 0)if display_dst(DELAY_BLUR) != 0:return 0 # Applying Median blurif display_caption('Median Blur') != 0:return 0 for i in range(1, MAX_KERNEL_LENGTH, 2):dst = cv.medianBlur(src, i)if display_dst(DELAY_BLUR) != 0:return 0 # Applying Bilateral Filterif display_caption('Bilateral Blur') != 0:return 0 for i in range(1, MAX_KERNEL_LENGTH, 2):dst = cv.bilateralFilter(src, i, i * 2, i / 2)if display_dst(DELAY_BLUR) != 0:return 0 # Donedisplay_caption('Done!')return 0 def display_caption(caption):global dstdst = np.zeros(src.shape, src.dtype)rows, cols, _ch = src.shapecv.putText(dst, caption,(int(cols / 4), int(rows / 2)),cv.FONT_HERSHEY_COMPLEX, 1, (255, 255, 255))return display_dst(DELAY_CAPTION) def display_dst(delay):cv.imshow(window_name, dst)c = cv.waitKey(delay)if c >= 0 : return -1return 0 if __name__ == "__main__":main(sys.argv[1:])
解释
让我们检查一下仅涉及平滑过程的 OpenCV 函数,因为其余的现在已经知道了。
归一化块滤波器:
- OpenCV 提供了函数 blur() 来使用此过滤器执行平滑处理。我们指定 4 个参数(更多详细信息,请查看参考):
- src:源图片
- dst:目标图像
- Size( w, h ):定义要使用的内核的大小(宽度 w 像素和高度 h 像素)
- Point(-1, -1):指示锚点(评估的像素)相对于邻域的位置。如果存在负值,则将内核的中心视为锚点。
- C++:
for i in range(1, MAX_KERNEL_LENGTH, 2):dst = cv.blur(src, (i, i))if display_dst(DELAY_BLUR) != 0:return 0
Java:
for (int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2) {Imgproc.blur(src, dst, new Size(i, i), new Point(-1, -1));displayDst(DELAY_BLUR);}
Python:
for i in range(1, MAX_KERNEL_LENGTH, 2):dst = cv.blur(src, (i, i))if display_dst(DELAY_BLUR) != 0:return 0
Gaussian Filter:
- 它由函数 GaussianBlur() 执行: 这里我们使用 4 个参数(更多细节,请查看 OpenCV 参考):
C++:
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){GaussianBlur( src, dst, Size( i, i ), 0, 0 );if( display_dst( DELAY_BLUR ) != 0 ){return 0;}}
Java:
for (int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2) {Imgproc.blur(src, dst, new Size(i, i), new Point(-1, -1));displayDst(DELAY_BLUR);}
python:
for i in range(1, MAX_KERNEL_LENGTH, 2):dst = cv.GaussianBlur(src, (i, i), 0)if display_dst(DELAY_BLUR) != 0:return 0
中值过滤器:
- 此过滤器由 medianBlur()函数提供: 我们使用三个参数:
- src:源图片
- dst:目标镜像,必须与 src 类型相同
- i:内核的大小(只有一个,因为我们使用方形窗口)。一定是奇怪的。
C++:
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){medianBlur ( src, dst, i );if( display_dst( DELAY_BLUR ) != 0 ){return 0;}}
Java:
for (int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2) {Imgproc.medianBlur(src, dst, i);displayDst(DELAY_BLUR);}
Python:
for i in range(1, MAX_KERNEL_LENGTH, 2):dst = cv.medianBlur(src, i)if display_dst(DELAY_BLUR) != 0:return 0
双边过滤器
- 由 OpenCV 函数 bilateralFilter() 提供 我们使用 5 个参数:
- src:源图片
- dst:目标图像
- d:每个像素邻域的直径。
C++:
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){bilateralFilter ( src, dst, i, i*2, i/2 );if( display_dst( DELAY_BLUR ) != 0 ){return 0;}}
Java:
for (int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2) {Imgproc.bilateralFilter(src, dst, i, i * 2, i / 2);displayDst(DELAY_BLUR);}
Python:
# Remember, bilateral is a bit slow, so as value go higher, it takes long timefor i in range(1, MAX_KERNEL_LENGTH, 2):dst = cv.bilateralFilter(src, i, i * 2, i / 2)if display_dst(DELAY_BLUR) != 0:return 0
结果
- 该代码打开一个图像(在本例中为 lena.jpg),并在解释的 4 个过滤器的影响下显示它。
- 以下是使用 medianBlur 平滑处理的图像快照:
参考文献:
1、《Smoothing Images》--------Ana Huamán