Opencv库的imgproc模块提供了很多经典的图像滤波函数,比如双边滤波、高斯滤波、Box滤波等等,同时也支持自定义滤波。
本节中描述的函数和类用于对2D图像(表示为Mat)进行各种线性或非线性滤波操作。 这意味着对于源图像(通常为矩形)中的每个像素位置(x,y),都将考虑其邻域并将其用于计算响应。 对于线性滤波器,它是像素值的加权和。 在形态操作的情况下,它是最小值或最大值,依此类推。 计算出的响应存储在目标图像中相同位置(x,y)处。 这意味着输出图像将具有与输入图像相同的尺寸。 通常,这些功能支持多通道数组,在这种情况下,每个通道都是独立处理的。 因此,输出图像还将具有与输入图像相同数量的通道。
cv::bilateralFilter函数对输入进行双边滤波。双边滤波可以参考论文http://www.dai.ed.ac.uk/CVonline/LOCAL_COPIES/MANDUCHI1/Bilateral_Filtering.html,或博客数字图像处理Python语言实现-图像增强-双边滤波(Bilateral Filter)。
双边滤波可以很好地减少不必要的噪声,同时保持边缘相当清晰。 但是,与大多数过滤器相比,它非常慢。
void cv::bilateralFilter(InputArray src,OutputArray dst,int d,double sigmaColor,double sigmaSpace,int borderType = BORDER_DEFAULT)
其中参数:
对于sigma值,可以将2个西格玛值设置为相同。 如果它们很小(<10),则滤镜效果不大;而如果它们很大(> 150),则滤镜效果会很强,使图像看起来“卡通化”。
对滤波窗口大小,d> 5时非常慢,因此建议对于实时应用程序使用d = 5,对于需要重噪声过滤的离线应用程序建议使用d = 9。
#include
#include
using namespace std;
int sigmaColor = 3;
int sigmaSpace = 3;
int kernelSize = 3;
void applyFilter();
void onSigmaColorChanged(int pos,void* userData);
void onSigmaSpaceChanged(int pos,void* userData);
void onKernelSizeChanged(int pos,void* userData);
cv::Mat image,dst;
void applyFilter(){
double sc = (double)sigmaColor / 10.0;
double sp = (double)sigmaSpace / 10.0;
int d = (int)kernelSize / 10;
cout << "sigmaColor = " << sc << ",sigmaSpace = " << sp << ",d = " << d << endl;
cv::bilateralFilter(image,dst,d,sc,sp);
cv::imshow("dst",dst);
}
void onSigmaColorChanged(int pos,void* userData){
applyFilter();
}
void onSigmaSpaceChanged(int pos,void* userData){
applyFilter();
}
void onKernelSizeChanged(int pos,void* userData){
kernelSize = 2 * kernelSize + 1;
applyFilter();
}
using namespace std;
int main()
{
// 读取图片
image = cv::imread("images/f1.jpg");
if(image.empty()){
cerr << "cannot read image from file\n";
exit(0);
}
// 创建窗口
cv::namedWindow("image");
// 创建TraceBar
cv::createTrackbar("Sigma Color","image",&sigmaColor,255,onSigmaColorChanged);
cv::createTrackbar("Sigma Space","image",&sigmaSpace,255,onSigmaSpaceChanged);
cv::createTrackbar("Kernel Size","image",&kernelSize,255,onKernelSizeChanged);
cv::imshow("image",image);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
程序结果结果
1)cv::boxFilter:使用方框滤波(Box Filter)模糊图像。
void cv::boxFilter(InputArray src,OutputArray dst,int ddepth,Size ksize,Point anchor = Point(-1,-1),bool normalize = true,int borderType = BORDER_DEFAULT)
该函数按以下方式模糊图像:
K = α [ 1 1 1 ⋯ 1 1 1 1 1 ⋯ 1 1 ⋯ 1 1 1 ⋯ 1 1 ] \texttt{K} = \alpha \begin{bmatrix} 1 & 1 & 1 & \cdots & 1 & 1 \\ 1 & 1 & 1 & \cdots & 1 & 1 \\ \cdots \\ 1 & 1 & 1 & \cdots & 1 & 1 \end{bmatrix} K=α⎣⎢⎢⎡11⋯1111111⋯⋯⋯111111⎦⎥⎥⎤,
其中, α = { 1 ksize.width*ksize.height when normalize=true 1 otherwise \alpha = \begin{cases} \frac{1}{\texttt{ksize.width*ksize.height}} & \texttt{when } \texttt{normalize=true} \\1 & \texttt{otherwise}\end{cases} α={ ksize.width*ksize.height11when normalize=trueotherwise
未归一化的框滤波器可用于计算每个像素邻域上的各种积分特性,例如图像导数的协方差矩阵(用于密集光流算法等)。 如果需要计算可变大小窗口上的像素总和,请使用积分。
参数描述如下:
2)cv::blur:使用归一化框滤镜模糊图像。
void cv::blur(InputArray src,OutputArray dst,Size ksize,Point anchor = Point(-1,-1),int borderType = BORDER_DEFAULT)
该函数使用如下内核对图像进行平滑处理:
K = 1 ksize.width*ksize.height [ 1 1 1 ⋯ 1 1 1 1 1 ⋯ 1 1 ⋯ 1 1 1 ⋯ 1 1 ] \texttt{K} = \frac{1}{\texttt{ksize.width*ksize.height}} \begin{bmatrix} 1 & 1 & 1 & \cdots & 1 & 1 \\ 1 & 1 & 1 & \cdots & 1 & 1 \\ \cdots \\ 1 & 1 & 1 & \cdots & 1 & 1 \\ \end{bmatrix} K=ksize.width*ksize.height1⎣⎢⎢⎡11⋯1111111⋯⋯⋯111111⎦⎥⎥⎤
调用blur(src,dst,ksize,anchor,borderType)等效于boxFilter(src,dst,src.type(),ksize,anchor,true,borderType)。
#include
#include
using namespace std;
int kernelSize = 3;
void applyFilter();
void onKernelSizeChanged(int pos,void* userData);
cv::Mat image,dstBoxFilter,dstBlur;
void applyFilter(){
// 方框滤波,默认归一化处理
cv::boxFilter(image,dstBoxFilter,-1,cv::Size(kernelSize,kernelSize));
// 图像模糊
cv::blur(image,dstBlur,cv::Size(kernelSize,kernelSize));
cv::imshow("boxFilter",dstBoxFilter);
cv::imshow("blur",dstBlur);
}
void onKernelSizeChanged(int pos,void* userData){
kernelSize = 2 * kernelSize + 1;
applyFilter();
}
using namespace std;
int main()
{
// 读取图片
image = cv::imread("resources/images/f1.jpg");
if(image.empty()){
cerr << "cannot read image from file\n";
exit(0);
}
// 创建窗口
cv::namedWindow("image");
// 创建TraceBar
cv::createTrackbar("Kernel Size","image",&kernelSize,15,onKernelSizeChanged);
cv::imshow("image",image);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
3)cv::sqrBoxFilter:计算与滤波器重叠的像素值的平方和。其效果与boxFilter区别不大。
void cv::sqrBoxFilter(InputArray src,OutputArray dst,int ddepth,Size ksize,Point anchor = Point(-1, -1),bool normalize = true,int borderType = BORDER_DEFAULT)
对于源图像中的每个像素(x,y),该函数都会计算与放置在像素(x,y)上的滤波器重叠的那些相邻像素值的平方和。
未归一化的方框滤波器(Box Filter)可用于计算局部图像统计信息,例如像素附近的局部方差和标准偏差。
#include
#include
using namespace std;
int kernelSize = 3;
void applyFilter();
void onKernelSizeChanged(int pos,void* userData);
cv::Mat image,dstBoxFilter,dstBlur;
void applyFilter(){
// sqrBox滤波
cv::sqrBoxFilter(image,dstBoxFilter,-1,cv::Size(kernelSize,kernelSize));
// 转换为8UC3类型
cv::convertScaleAbs(dstBoxFilter,dstBoxFilter);
cv::imshow("image",dstBoxFilter);
}
void onKernelSizeChanged(int pos,void* userData){
kernelSize = 2 * kernelSize + 1;
applyFilter();
}
using namespace std;
int main()
{
// 读取图片
image = cv::imread("images/f1.jpg");
if(image.empty()){
cerr << "cannot read image from file\n";
exit(0);
}
// 创建窗口
cv::namedWindow("image");
// 创建TraceBar
cv::createTrackbar("Kernel Size","image",&kernelSize,15,onKernelSizeChanged);
cv::imshow("image",image);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
1)cv::buildPyramid:用于构建图像高斯金字塔。
void cv::buildPyramid(InputArray src,OutputArrayOfArrays dst,int maxlevel,int borderType = BORDER_DEFAULT)
该函数从dst [0] == src开始,将pyrDown递归地应用于先前构建的金字塔层,从而构造图像矢量并构建高斯金字塔。
关于高斯金字塔的详解,可以参考维基百科。
参数详解如下:
#include
#include
using namespace std;
int main()
{
// 读取图像
cv::Mat image = cv::imread("resources/images/f1.jpg");
if(image.empty()){
cerr << "cannot open image from file.\n";
exit(0);
}
// 构建高斯金字塔
std::vector dst;
int maxLevel = 4;
cv::buildPyramid(image,dst,maxLevel);
// 显示不同层次的高斯金字塔
for(int i = 0;i < maxLevel + 1;i++){
stringstream title;
title << "pyramid:" << i;
std::string winName = title.str();
cv::imshow(winName,dst[i]);
}
cv::imshow("image-src",image);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YdwNqsrA-1609749446031)(images/04-1-3.png)]
2)、cv::pyrDown:模糊图像并对其进行下采样。
void cv::pyrDown(InputArray src,OutputArray dst,const Size & dstsize = Size(),int borderType = BORDER_DEFAULT)
默认情况下,输出图像的大小计算为Size((src.cols + 1)/ 2,(src.rows + 1)/ 2),但是在任何情况下,都应满足以下条件:
∣ dstsize.width ∗ 2 − s r c . c o l s ∣ ≤ 2 ∣ dstsize.height ∗ 2 − s r c . r o w s ∣ ≤ 2 \begin{array}{l} | \texttt{dstsize.width} *2-src.cols| \leq 2 \\ | \texttt{dstsize.height} *2-src.rows| \leq 2 \end{array} ∣dstsize.width∗2−src.cols∣≤2∣dstsize.height∗2−src.rows∣≤2
该函数执行高斯金字塔构造的下采样步骤。 首先,它将源映像与内核进行卷积:
1 256 [ 1 4 6 4 1 4 16 24 16 4 6 24 36 24 6 4 16 24 16 4 1 4 6 4 1 ] \frac{1}{256} \begin{bmatrix} 1 & 4 & 6 & 4 & 1 \\ 4 & 16 & 24 & 16 & 4 \\ 6 & 24 & 36 & 24 & 6 \\ 4 & 16 & 24 & 16 & 4 \\ 1 & 4 & 6 & 4 & 1 \end{bmatrix} 2561⎣⎢⎢⎢⎢⎡1464141624164624362464162416414641⎦⎥⎥⎥⎥⎤
然后,它通过拒绝偶数行和列对图像进行下采样。
参数说明:
3)、cv::pyUp:上采样图像,然后使其模糊。
void cv::pyrUp(InputArray src,OutputArray dst,const Size & dstsize = Size(),int borderType = BORDER_DEFAULT)
默认情况下,输出图像的大小计算为Size(src.cols \ * 2,(src.rows \ * 2),但是在任何情况下,都应满足以下条件:
∣ dstsize.width − s r c . c o l s ∗ 2 ∣ ≤ ( dstsize.width m o d 2 ) ∣ dstsize.height − s r c . r o w s ∗ 2 ∣ ≤ ( dstsize.height m o d 2 ) \begin{array}{l} | \texttt{dstsize.width} -src.cols*2| \leq ( \texttt{dstsize.width} \mod 2) \\ | \texttt{dstsize.height} -src.rows*2| \leq ( \texttt{dstsize.height} \mod 2) \end{array} ∣dstsize.width−src.cols∗2∣≤(dstsize.widthmod2)∣dstsize.height−src.rows∗2∣≤(dstsize.heightmod2)
该函数执行高斯金字塔构造的升采样步骤,尽管它实际上可以用于构造拉普拉斯金字塔。 首先,它甚至通过注入零行和零列来对源图像进行升采样,然后将结果与与pyrDown中相同的内核乘以4进行卷积。
参数描述:
#include
#include
using namespace std;
int main()
{
// 读取图像
cv::Mat image = cv::imread("images/f1.jpg");
if(image.empty()){
cerr << "cannot open image from file.\n";
return EXIT_FAILURE;
}
cv::Mat dst;
char key = -1;
while (true) {
cv::imshow("src",image);
key = cv::waitKey(10);
if(key == 27){
break;
}
if(key == 'i'){
// 向上采样
cv::pyrUp(image,dst,cv::Size(image.cols*2,image.rows*2));
cv::imshow("dst",dst);
}
if(key == 'o'){
// 向下采样
cv::pyrDown(image,dst,cv::Size(image.cols / 2,image.rows / 2));
cv::imshow("dst",dst);
}
}
cv::destroyAllWindows();
return 0;
}
1)cv::getStructuringElement:返回指定大小和形状的结构元素以进行形态学操作。
Mat cv::getStructuringElement(int shape,Size ksize,Point anchor = Point(-1,-1))
该函数构造并返回可进一步传递给erode、dilate或morphologyEx的构造元素。 但是您也可以自己构造一个任意的二进制掩码,并将其用作构造元素。
参数如下:
shape:结构的形状,由MorphShapes指定,支持MORPH_RECT 、MORPH_CROSS 、MORPH_ELLIPSE 类型。
ksize:形状大小
anchor:元素内的锚位置。 默认值(-1,-1)表示锚点位于中心。 注意,只有十字形元件的形状取决于锚位置。 在其他情况下,锚点仅调节形态运算结果偏移了多少。
// 创建矩形结构
cv::Mat rect = cv::getStructuringElement(cv::MorphShapes::MORPH_RECT,cv::Size(128,128));
// 创建十字结构
cv::Mat cross = cv::getStructuringElement(cv::MorphShapes::MORPH_CROSS,cv::Size(128,128));
// 创建椭圆结构
cv::Mat eclipse = cv::getStructuringElement(cv::MorphShapes::MORPH_ELLIPSE,cv::Size(128,128));
2)cv::dilate:通过使用特定的结构元素来膨胀图像。
void cv::dilate(InputArray src,OutputArray dst,InputArray kernel,Point anchor = Point(-1,-1),int iterations = 1,int borderType = BORDER_CONSTANT,const Scalar & borderValue = morphologyDefaultBorderValue())
该函数使用指定的结构化元素来膨胀源图像,该结构化元素确定在其上获取最大值的像素邻域的形状:
dst ( x , y ) = max ( x ′ , y ′ ) : element ( x ′ , y ′ ) ≠ 0 src ( x + x ′ , y + y ′ ) \texttt{dst} (x,y) = \max_{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y') dst(x,y)=max(x′,y′):element(x′,y′)=0src(x+x′,y+y′)
该函数支持in-place模式。 可以进行几次(迭代)膨胀。 在多通道图像的情况下,每个通道都是独立处理的。参数如下:
#include
#include
using namespace std;
int kerneSize = 3,iterations = 1;
cv::Mat image,dst,element;
int elemType = cv::MorphShapes::MORPH_RECT;
// 对图像进行膨胀操作
void apply(){
int ksize = kerneSize * 2 + 1;
element = cv::getStructuringElement(elemType,cv::Size(ksize,ksize));
cv::dilate(image,dst,element,cv::Point(-1,-1),iterations);
cv::imshow("image",dst);
}
void onKernelSizeChanged(int pos,void* userData){
apply();
}
void onIterationChanged(int pos,void* userData){
apply();
}
int main()
{
// 读取图像
image = cv::imread("images/f1.jpg");
if(image.empty()){
cerr << "cannot open image from file.\n";
return EXIT_FAILURE;
}
// 创建窗口与TraceBar
cv::namedWindow("image");
cv::createTrackbar("kerenel:2n+1","image",&kerneSize,16,onKernelSizeChanged);
cv::createTrackbar("iterations","image",&iterations,100,onIterationChanged);
cv::setTrackbarMin("iterations","image",1);
cv::imshow("image",image);
char key = 0;
while(true){
key = cv::waitKey(10);
// 切换不同形状
if(key == 'r'){
elemType = cv::MorphShapes::MORPH_ELLIPSE;
apply();
}
if(key == 'c'){
elemType = cv::MorphShapes::MORPH_CROSS;
apply();
}
if(key == 'e'){
elemType = cv::MorphShapes::MORPH_ELLIPSE;
apply();
}
if(key == 27){
break;
}
}
return 0;
}
3)cv::erode:通过使用特定的结构元素来腐蚀图像。
void cv::erode(InputArray src,OutputArray dst,InputArray kernel,Point anchor = Point(-1,-1),int iterations = 1,int borderType = BORDER_CONSTANT,const Scalar & borderValue = morphologyDefaultBorderValue())
该函数使用指定的结构化元素腐蚀源图像,该结构化元素确定在其上采用最小值的像素邻域的形状:
dst ( x , y ) = min ( x ′ , y ′ ) : element ( x ′ , y ′ ) ≠ 0 src ( x + x ′ , y + y ′ ) \texttt{dst} (x,y) = \min _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y') dst(x,y)=min(x′,y′):element(x′,y′)=0src(x+x′,y+y′)
该函数支持in-place模式。 可以进行几次(迭代)腐蚀。 在多通道图像的情况下,每个通道都是独立处理的。参数如下:
#include
#include
using namespace std;
int kerneSize = 3,iterations = 1;
cv::Mat image,dst,element;
int elemType = cv::MorphShapes::MORPH_RECT;
// 对图像进行腐蚀操作
void apply(){
int ksize = kerneSize * 2 + 1;
element = cv::getStructuringElement(elemType,cv::Size(ksize,ksize));
cv::erode(image,dst,element,cv::Point(-1,-1),iterations);
cv::imshow("image",dst);
}
void onKernelSizeChanged(int pos,void* userData){
apply();
}
void onIterationChanged(int pos,void* userData){
apply();
}
int main()
{
// 读取图像
image = cv::imread("images/f1.jpg");
if(image.empty()){
cerr << "cannot open image from file.\n";
return EXIT_FAILURE;
}
// 创建窗口与TraceBar
cv::namedWindow("image");
cv::createTrackbar("kerenel:2n+1","image",&kerneSize,16,onKernelSizeChanged);
cv::createTrackbar("iterations","image",&iterations,100,onIterationChanged);
cv::setTrackbarMin("iterations","image",1);
cv::imshow("image",image);
char key = 0;
while(true){
key = cv::waitKey(10);
// 切换不同形状
if(key == 'r'){
elemType = cv::MorphShapes::MORPH_ELLIPSE;
apply();
}
if(key == 'c'){
elemType = cv::MorphShapes::MORPH_CROSS;
apply();
}
if(key == 'e'){
elemType = cv::MorphShapes::MORPH_ELLIPSE;
apply();
}
if(key == 27){
break;
}
}
return 0;
}
4)cv::morphologyEx:执行高级形态学变换。
void cv::morphologyEx(InputArray src,OutputArray dst,int op,InputArray kernel,Point anchor = Point(-1,-1),int iterations = 1,int borderType = BORDER_CONSTANT,const Scalar & borderValue = morphologyDefaultBorderValue())
函数cv :: morphologyEx可以使用侵蚀和膨胀作为基本操作来执行高级形态转换。
任何操作都可以就地(in-place)完成。 在多通道图像的情况下,每个通道都是独立处理的。参数如下:
src:输入图像; 通道数可以是任意的,但深度应为CV_8U,CV_16U,CV_16S,CV_32F或CV_64F之一。
dst:输出图像;与src具有相同大小和类型的图像。
op:形态学操作的类型。MorphTypes支持的类型如下:
**MORPH_ERODE:**腐蚀操作,与cv::erode相同
**MORPH_DILATE:**膨胀操作,与cv::dilate相同
**MORPH_OPEN:**形态学开操作。 dst = o p e n ( src , element ) = d i l a t e ( e r o d e ( src , element ) ) \texttt{dst} = \mathrm{open} ( \texttt{src} , \texttt{element} )= \mathrm{dilate} ( \mathrm{erode} ( \texttt{src} , \texttt{element} )) dst=open(src,element)=dilate(erode(src,element))
**MORPH_CLOSE:**形态学闭操作。 dst = c l o s e ( src , element ) = e r o d e ( d i l a t e ( src , element ) ) \texttt{dst} = \mathrm{close} ( \texttt{src} , \texttt{element} )= \mathrm{erode} ( \mathrm{dilate} ( \texttt{src} , \texttt{element} )) dst=close(src,element)=erode(dilate(src,element))
**MORPH_GRADIENT:**形态学梯度操作。 dst = m o r p h _ g r a d ( src , element ) = d i l a t e ( src , element ) − e r o d e ( src , element ) \texttt{dst} = \mathrm{morph\_grad} ( \texttt{src} , \texttt{element} )= \mathrm{dilate} ( \texttt{src} , \texttt{element} )- \mathrm{erode} ( \texttt{src} , \texttt{element} ) dst=morph_grad(src,element)=dilate(src,element)−erode(src,element)
**MORPH_TOPHAT:**形态学顶帽操作。 dst = t o p h a t ( src , element ) = src − o p e n ( src , element ) \texttt{dst} = \mathrm{tophat} ( \texttt{src} , \texttt{element} )= \texttt{src} - \mathrm{open} ( \texttt{src} , \texttt{element} ) dst=tophat(src,element)=src−open(src,element)
**MORPH_BLACKHAT:**形态学黑帽操作。 dst = b l a c k h a t ( src , element ) = c l o s e ( src , element ) − src \texttt{dst} = \mathrm{blackhat} ( \texttt{src} , \texttt{element} )= \mathrm{close} ( \texttt{src} , \texttt{element} )- \texttt{src} dst=blackhat(src,element)=close(src,element)−src
**MORPH_HITMISS:**形态学击中与不击中操作。只对CV_8UC1图像有效
kernel:用于腐蚀的结构元素; 如果elemenat = Mat(),则使用3 x 3的矩形结构元素。 可以使用getStructuringElement创建结构元素。
anchor:锚在元素内的位置; 默认值(-1,-1)表示锚点位于元素中心。
iterations:迭代次数
borderType:像素外推法,可以参考BorderTypes。 不支持BORDER WRAP。
borderValue:边界类型常量时的边界值。
#include
#include
using namespace std;
int kerneSize = 3,iterations = 1;
cv::Mat image,dst,element;
int elemType = cv::MorphShapes::MORPH_RECT;
int operationType = cv::MORPH_ERODE;
// 对图像进行腐蚀操作
void apply(){
int ksize = kerneSize * 2 + 1;
element = cv::getStructuringElement(elemType,cv::Size(ksize,ksize));
cv::morphologyEx(image,dst,operationType,element,cv::Point(-1,-1),iterations);
cv::imshow("image",dst);
}
void onKernelSizeChanged(int pos,void* userData){
apply();
}
void onIterationChanged(int pos,void* userData){
apply();
}
int main()
{
// 读取图像
image = cv::imread("images/f1.jpg",0);
if(image.empty()){
cerr << "cannot open image from file.\n";
return EXIT_FAILURE;
}
// 创建窗口与TraceBar
cv::namedWindow("image");
cv::createTrackbar("kerenel:2n+1","image",&kerneSize,16,onKernelSizeChanged);
cv::createTrackbar("iterations","image",&iterations,100,onIterationChanged);
cv::setTrackbarMin("iterations","image",1);
cv::imshow("image",image);
char key = 0;
while(true){
key = cv::waitKey(10);
// 切换不同形状
if(key == 'r'){
elemType = cv::MorphShapes::MORPH_ELLIPSE;
apply();
}
if(key == 'c'){
elemType = cv::MorphShapes::MORPH_CROSS;
apply();
}
if(key == 'e'){
elemType = cv::MorphShapes::MORPH_ELLIPSE;
apply();
}
if(key == 27){
break;
}
if(key == '1'){
operationType = cv::MORPH_ERODE;
apply();
}
if(key == '2'){
operationType = cv::MORPH_DILATE;
apply();
}
if(key == '3'){
operationType = cv::MORPH_OPEN;
apply();
}
if(key == '4'){
operationType = cv::MORPH_CLOSE;
apply();
}
if(key == '5'){
operationType = cv::MORPH_TOPHAT;
apply();
}
if(key == '6'){
operationType = cv::MORPH_BLACKHAT;
apply();
}
if(key == '7'){
operationType = cv::MORPH_ERODE;
apply();
}
if(key == '8'){
operationType = cv::MORPH_HITMISS;
apply();
}
}
return 0;
}
1)cv::filter2D:将图像与内核卷积。
void cv::filter2D(InputArray src,OutputArray dst,int ddepth,InputArray kernel,Point anchor = Point(-1,-1),double delta = 0,int borderType = BORDER_DEFAULT)
该函数将任意线性滤波器应用于图像。 支持就地操作。 当窗口部分位于图像外部时,该函数会根据指定的边框模式对异常像素值进行插值。
该函数实际上计算相关性,而不是卷积:
KaTeX parse error: Undefined control sequence: \substack at position 30: …x,y) = \sum _{ \̲s̲u̲b̲s̲t̲a̲c̲k̲{0\leq x' < \te…
也就是说,内核不会围绕定位点进行镜像。 如果需要真正的卷积,请使用flip翻转内核,并将新锚点设置为(kernel.cols-anchor.x-1,kernel.rows-anchor.y-1)。
在内核足够大(〜11 x 11或更大)的情况下,该函数使用基于DFT的算法,而对于小内核则使用直接算法。
参数如下:
#include
#include
using namespace std;
int main()
{
// 读取图像
cv::Mat image = cv::imread("images/f1.jpg");
if(image.empty()){
cerr << "cannot read image from file.\n";
return EXIT_FAILURE;
}
cv::namedWindow("image", cv::WINDOW_AUTOSIZE);
cv::imshow("image", image);
// 创建卷积内核
cv::Mat kernel = (cv::Mat_(3,3) << 0, -1 ,0,
-1, 5, -1,
0, -1, 0);
// 执行卷积操作
cv::Mat dst;
cv::filter2D(image,dst,-1,kernel);
imshow("filter2d",dst);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
常见操作的内核有:
2)cv::sepFilter2D:对图像应用可分离的线性滤波器。
void cv::sepFilter2D(InputArray src,OutputArray dst,int ddepth,InputArray kernelX,InputArray kernelY,Point anchor = Point(-1,-1),double delta = 0,int borderType = BORDER_DEFAULT)
该功能将可分离的线性滤波器应用于图像。 也就是说,首先,使用1D内核kernelX过滤src的每一行。 然后,结果的每一列都使用一维内核kernelY进行过滤。 偏移了delta的最终结果存储在dst中。
参数如下:
#include
#include
using namespace std;
int main()
{
// 读取图像
cv::Mat image = cv::imread("images/f1.jpg");
if(image.empty()){
cerr << "cannot open image from file.\n";
return EXIT_FAILURE;
}
cv::Mat kx = (cv::Mat_(1, 3) << 0, -1, 0);
cv::Mat ky = (cv::Mat_(1, 3) << -1, 0, -1);
// 执行滤波
cv::Mat dst,imageF;
image.convertTo(imageF,CV_32FC3);
cv::sepFilter2D(imageF, dst, -1, kx, ky);
cv::convertScaleAbs(dst,dst);
cv::imshow("src",image);
cv::imshow("dst",dst);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
1)cv::GaussianBlur:对图像进行高斯模糊处理。
void cv::GaussianBlur(InputArray src,OutputArray dst,Size ksize,double sigmaX,double sigmaY = 0,int borderType = BORDER_DEFAULT)
该函数将源图像与指定的高斯内核进行卷积。支持就地过滤。
参数描述如下:
#include
#include
using namespace std;
int kerneSize = 3,sigmaX = 0,sigmaY = 0;
cv::Mat image,dst,element;
int elemType = cv::MorphShapes::MORPH_RECT;
// 对图像进行高斯模糊操作
void apply(){
int ksize = kerneSize * 2 + 1;
double sgx = sigmaX / 10.0;
double sgy = sigmaY / 10.0;
cv::GaussianBlur(image,dst,cv::Size(ksize,ksize),sgx,sgy);
cv::imshow("image",dst);
}
void onKernelSizeChanged(int pos,void* userData){
apply();
}
void onSigmaXChanged(int pos,void* userData){
apply();
}
void onSigmaYChanged(int pos,void* userData){
apply();
}
int main()
{
// 读取图像
image = cv::imread("images/f1.jpg");
if(image.empty()){
cerr << "cannot open image from file.\n";
return EXIT_FAILURE;
}
// 创建窗口与TraceBar
cv::namedWindow("image");
cv::createTrackbar("kerenel:2n+1","image",&kerneSize,16,onKernelSizeChanged);
cv::createTrackbar("sigmaX/10","image",&sigmaX,100,onSigmaXChanged);
cv::createTrackbar("sigmaY/10","image",&sigmaY,100,onSigmaYChanged);
cv::imshow("image",image);
char key = 0;
while(true){
key = cv::waitKey(10);
if(key == 27){
break;
}
}
return 0;
}
2)cv::getGaussianKernel:返回高斯滤波器系数。
Mat cv::getGaussianKernel(int ksize,double sigma,int ktype = CV_64F)
该函数计算并返回高斯滤波器系数的ksize×1矩阵:
G i = α ∗ e − ( i − ( ksize − 1 ) / 2 ) 2 / ( 2 ∗ sigma 2 ) , G_i= \alpha *e^{-(i-( \texttt{ksize} -1)/2)^2/(2* \texttt{sigma}^2)}, Gi=α∗e−(i−(ksize−1)/2)2/(2∗sigma2),
其中, i = 0.. ksize − 1 i=0..\texttt{ksize}-1 i=0..ksize−1和 α \alpha α是选择的比例因子,因此 ∑ i G i = 1 \sum_i G_i=1 ∑iGi=1
可以将其中两个这样生成的内核传递给sepFilter2D。 这些函数自动识别平滑核(权重之和等于1的对称核)并相应地进行处理。 也可以使用更高级别的GaussianBlur。
#include
#include
#include
#include
using namespace std;
// 生成2D高斯内核
cv::Mat getGaussianKernel2D(int rows, int cols, double sigmax, double sigmay)
{
auto gauss_x = cv::getGaussianKernel(cols, sigmax, CV_32F);
auto gauss_y = cv::getGaussianKernel(rows, sigmay, CV_32F);
return gauss_x * gauss_y.t();
}
int main()
{
// 读取图像
cv::Mat image = cv::imread("images/f1.jpg");
if(image.empty()){
cerr << "cannot open image from file.\n";
return EXIT_FAILURE;
}
// 生成一维高斯内核
cv::Mat kernel = cv::getGaussianKernel(3,2.5);
cout << "kernel = " << kernel << endl;
cv::Mat kernel2D = getGaussianKernel2D(7,7,2.5,2.5);
cout << "kernel2D = " << kernel2D << endl;
// 对高斯内核进行卷积
cv::Mat dst;
cv::filter2D(image,dst,-1,kernel2D);
cv::Mat dstGaussian;
// 相同参数的高斯模糊
cv::GaussianBlur(image,dstGaussian,cv::Size(7,7),2.5,2.5);
// 显示图像
cv::imshow("image",image);
cv::imshow("dstFilter2D",dst);
cv::imshow("dstGaussian",dstGaussian);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
返回Gabor滤波器系数。
*Mat cv::getGaborKernel(Size ksize,double sigma,double theta,double lambd,double gamma,double psi = CV_PI 0.5int ktype = CV_64F)
有关gabor滤波器方程式和参数的更多详细信息,请参见:Gabor Filter.
参数如下:
参数名称 | 说明 |
---|---|
ksize | 返回的过滤器的大小 |
sigma | 高斯包络线的标准偏差。 |
theta | 法线与Gabor函数的平行条纹的方向。 |
lambd | 正弦因子的波长。 |
gamma | 空间纵横比。 |
psi | 相位偏移。 |
ktype | 滤波器系数的类型。 它可以是CV_32F或CV_64F。 |
#include
#include
using namespace std;
const string WIN_NAME = "gabor filter";
cv::Mat image,dst,kernel;
int kernelSize = 3;
int sigmaI = 0;
int thetaI = 0;
int lambdI = 0;
int gammaI = 0;
void apply(){
int ksize = kernelSize * 2 + 1;
double sigma = sigmaI / 10.0;
double theta = thetaI / 10.0;
double lambd = lambdI / 10.0;
double gamma = gammaI / 10.0;
// 获取Gabor滤波内核
kernel = cv::getGaborKernel(cv::Size(ksize,ksize),sigma,theta,lambd,gamma);
// 执行滤波
cv::filter2D(image,dst,-1,kernel);
cv::imshow(WIN_NAME,dst);
}
void onKernelSizeChanged(int pos,void* userData){
apply();
}
void onSigmaChanged(int pos,void* userData){
apply();
}
void onThetaChanged(int pos,void* userData){
apply();
}
void onLambdChanged(int pos,void* userData){
apply();
}
void onGammaChanged(int pos,void* userData){
apply();
}
int main()
{
// 读取图像
image = cv::imread("images/f1.jpg");
if(image.empty()){
cerr << "cannot open image from file.\n";
return EXIT_FAILURE;
}
// 创建窗口及TraceBar
cv::namedWindow(WIN_NAME);
cv::createTrackbar("kernelSize:2n+1",WIN_NAME,&kernelSize,16,onKernelSizeChanged);
cv::createTrackbar("sigma/10",WIN_NAME,&sigmaI,100,onSigmaChanged);
cv::createTrackbar("theta/10",WIN_NAME,&thetaI,100,onSigmaChanged);
cv::createTrackbar("lambd/10",WIN_NAME,&lambdI,100,onLambdChanged);
cv::createTrackbar("gamma/10",WIN_NAME,&gammaI,100,onGammaChanged);
cv::imshow(WIN_NAME,image);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
1)cv::getDerivKernels:返回用于计算空间图像导数的滤波器系数。
void cv::getDerivKernels(OutputArray kx,OutputArray ky,int dx,int dy,int ksize,bool normalize = false,int ktype = CV_32F)
该函数计算并返回空间图像导数的滤波器系数。 当ksize = FILTER_SCHARR时,将生成Scharr 3×3内核(请参见Scharr)。 否则,将生成Sobel内核(请参阅Sobel)。 过滤器通常传递给sepFilter2D。
参数如下:
参数名称 | 参数描述 |
---|---|
kx | 行滤波器系数的输出矩阵。 它的类型为ktype。 |
ky | 列滤波器系数的输出矩阵。 它的类型为ktype。 |
dx | 关于x的导数阶。 |
dy | 关于y的导数阶。 |
ksize | 光圈大小。 可以是FILTER_SCHARR,1、3、5或7。 |
normalize | 指示是否规范化(按比例缩小)滤波器系数的标志。 从理论上讲,系数应具有分母= 2ksize * 2-dx-dy-2。 如果要过滤浮点图像,则可能使用归一化的内核。 但是,如果您计算8位图像的导数,将结果存储在16位图像中,并希望保留所有小数位,则可能需要设置normalize = false。 |
ktype | 滤波器系数的类型。 它可以是CV_32f或CV_64F。 |
#include
#include
using namespace std;
int main()
{
// 读取图像
cv::Mat image = cv::imread("images/f1.jpg");
if(image.empty()){
cerr << "cannot open image from file.\n";
return EXIT_FAILURE;
}
// 执行图像滤波
cv::Mat kx, ky,dst;
cv::getDerivKernels(kx,ky,2,2,7,true);
cv::sepFilter2D(image,dst,-1,kx,ky);
cout << dst;
cv::imshow("image",image);
cv::imshow("dst",dst);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
2)cv::Scharr:使用Scharr运算符计算一阶x或y图像导数。
void cv::Scharr(InputArray src,OutputArray dst,int ddepth,int dx,int dy,double scale = 1,double delta = 0,int borderType = BORDER_DEFAULT)
该函数使用Scharr运算符计算第一x或y空间图像导数。
Scharr(src, dst, ddepth, dx, dy, scale, delta, borderType) \texttt{Scharr(src, dst, ddepth, dx, dy, scale, delta, borderType)} Scharr(src, dst, ddepth, dx, dy, scale, delta, borderType)
与下面的调用相同:
KaTeX parse error: Expected '}', got '_' at position 47: … dx, dy, FILTER_̲SCHARR, scale, …
参数如下:
参数名称 | 参数描述 |
---|---|
src | 输入图像 |
dst | 输出图像,与src的类型、大小相同 |
ddepth | 输出图像的深度 |
dx | 导数x的阶数。 |
dy | 导数y的阶数。 |
scale | 计算得出的导数值的可选比例因子; 默认情况下,不应用缩放(有关详细信息,请参见getDerivKernels。 |
delta | 在将结果存储到dst之前将其添加到结果中的可选增量值。 |
borderType | 像素外推方法,请参见BorderTypes。 不支持BORDER_WRAP。 |
#include
#include
using namespace std;
int main()
{
// 读取灰度图像
cv::Mat gray = cv::imread("images/f1.jpg",0);
if(gray.empty()){
cerr << "cannot open image from file.\n";
return EXIT_FAILURE;
}
cv::Mat dx,dy;
cv::Mat blurImage;
// 模糊图像
cv::blur(gray, blurImage,cv::Size(3,3));
// 对图像进行Scharr求导
Scharr(blurImage,dx,CV_16S,1,0);
Scharr(blurImage,dy,CV_16S,0,1);
// 显示图像
cv::imshow("image",gray);
cv::imshow("dx",dx);
cv::imshow("dy",dy);
cv::waitKey();
return 0;
}
3)cv::Sobel:使用扩展的Sobel运算符计算第一,第二,第三或混合图像导数。
void cv::Sobel(InputArray src,OutputArray dst,int ddepth,int dx,int dy,int ksize = 3,double scale = 1,double delta = 0,int borderType = BORDER_DEFAULT)
除一种情况外,在所有情况下,均使用ksize×ksize可分离内核来计算导数。 当ksize = 1时,使用3×1或1×3内核(即,不进行高斯平滑)。 ksize = 1只能用于一阶或二阶x或y导数。
还有一个特殊值ksize = FILTER_SCHARR(-1)对应于3×3 Scharr滤波器,它可能比3×3 Sobel给出更准确的结果。Scharr算子为: [ − 3 0 3 − 10 0 10 − 3 0 3 ] \begin{bmatrix} -3&0&3\\-10&0&10\\-3&0&3\\ \end{bmatrix} ⎣⎡−3−10−30003103⎦⎤
对于x导数,或换位为y导数。
该函数通过将图像与适当的内核进行卷积来计算图像导数:
dst = ∂ x o r d e r + y o r d e r src ∂ x x o r d e r ∂ y y o r d e r \texttt{dst} = \frac{\partial^{xorder+yorder} \texttt{src}}{\partial x^{xorder} \partial y^{yorder}} dst=∂xxorder∂yyorder∂xorder+yordersrc
Sobel算子将高斯平滑和微分相结合,因此结果或多或少具有抗噪性。 通常,使用(xorder = 1,yorder = 0,ksize = 3)或(xorder = 0,yorder = 1,ksize = 3)调用函数以计算第一个x或y图像导数。
第一种情况对应于以下内核:
[ − 1 0 1 − 2 0 2 − 1 0 1 ] \begin{bmatrix}-1&0&1\\-2&0&2\\-1&0&1\\ \end{bmatrix} ⎣⎡−1−2−1000121⎦⎤
第二种情况对应于以下内核:
[ − 1 − 2 − 1 0 0 0 1 2 1 ] \begin{bmatrix}-1&-2&-1\\0&0&0\\1&2&1 \end{bmatrix} ⎣⎡−101−202−101⎦⎤
参数如下:
参数名称 | 参数描述 |
---|---|
src | 输入图像 |
dst | 输出图像,与src的类型、大小相同 |
ddepth | 输出图像的深度 |
dx | 导数x的阶数。 |
dy | 导数y的阶数。 |
ksize | 扩展的Sobel内核的大小; 它必须是1、3、5或7。 |
scale | 计算得出的导数值的可选比例因子; 默认情况下,不应用缩放(有关详细信息,请参见getDerivKernels |
delta | 在将结果存储到dst之前将其添加到结果中的可选增量值。 |
borderType | 像素外推方法,请参见BorderTypes。 不支持BORDER_WRAP。 |
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include
using namespace cv;
using namespace std;
int main( int argc, char** argv )
{
cv::CommandLineParser parser(argc, argv,
"{@input |lena.jpg|input image}"
"{ksize k|1|ksize (hit 'K' to increase its value at run time)}"
"{scale s|1|scale (hit 'S' to increase its value at run time)}"
"{delta d|0|delta (hit 'D' to increase its value at run time)}"
"{help h|false|show help message}");
cout << "The sample uses Sobel or Scharr OpenCV functions for edge detection\n\n";
parser.printMessage();
cout << "\nPress 'ESC' to exit program.\nPress 'R' to reset values ( ksize will be -1 equal to Scharr function )";
// First we declare the variables we are going to use
Mat image,src, src_gray;
Mat grad;
const String window_name = "Sobel Demo - Simple Edge Detector";
int ksize = parser.get("ksize");
int scale = parser.get("scale");
int delta = parser.get("delta");
int ddepth = CV_16S;
String imageName = parser.get("@input");
// As usual we load our source image (src)
image = imread( samples::findFile( "d:/develop/machine-vision-workbench/resources/images/f1.jpg" ), IMREAD_COLOR ); // Load an image
// Check if image is loaded fine
if( image.empty() )
{
printf("Error opening image: %s\n", imageName.c_str());
return EXIT_FAILURE;
}
for (;;)
{
// Remove noise by blurring with a Gaussian filter ( kernel size = 3 )
GaussianBlur(image, src, Size(3, 3), 0, 0, BORDER_DEFAULT);
// Convert the image to grayscale
cvtColor(src, src_gray, COLOR_BGR2GRAY);
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
Sobel(src_gray, grad_x, ddepth, 1, 0, ksize, scale, delta, BORDER_DEFAULT);
Sobel(src_gray, grad_y, ddepth, 0, 1, ksize, scale, delta, BORDER_DEFAULT);
// converting back to CV_8U
convertScaleAbs(grad_x, abs_grad_x);
convertScaleAbs(grad_y, abs_grad_y);
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad);
imshow(window_name, grad);
char key = (char)waitKey(0);
if(key == 27)
{
return EXIT_SUCCESS;
}
if (key == 'k' || key == 'K')
{
ksize = ksize < 30 ? ksize+2 : -1;
}
if (key == 's' || key == 'S')
{
scale++;
}
if (key == 'd' || key == 'D')
{
delta++;
}
if (key == 'r' || key == 'R')
{
scale = 1;
ksize = -1;
delta = 0;
}
}
return EXIT_SUCCESS;
}
4)、cv::spatialGradient:使用Sobel运算符计算x和y中的一阶图像导数。
void cv::spatialGradient(InputArray src,OutputArray dy,int ksize = 3,int borderType = BORDER_DEFAULT)
相当于调用Sobel函数:
Sobel( src, dx, CV_16SC1, 1, 0, 3 );
Sobel( src, dy, CV_16SC1, 0, 1, 3 );
示例如下:
#include
#include
using namespace std;
int main()
{
// 读取灰度图像
cv::Mat src = cv::imread("images/f1.jpg",0);
if(src.empty()){
cerr << "cannot read imgage from file.\n";
return EXIT_FAILURE;
}
// 计算图像梯度
cv::Mat dx,dy,abs_dx,abs_dy,dst;
cv::spatialGradient(src,dx,dy);
// 转换成8UC1
convertScaleAbs(dx, abs_dx);
convertScaleAbs(dy, abs_dy);
addWeighted(abs_dx, 0.5, abs_dy, 0.5, 0, dst);
cv::imshow("src",src);
cv::imshow("dst",dst);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}