1、图像平滑处理
使用各种线性滤波器对图像进行平滑(模糊)处理,相关OpenCV函数如下:
- blur 归一化块滤波器
一些涉及到的函数: createTrackbar( 滚动条名称,窗口名称 ,位置 ,滚动条最大值 ,调用的函数名 )
Mat element = getStructuringElement(内核类型,内核大小,锚点位置);
在本例中,我们使用了4个参数(其余使用默认值):
它们的取值范围是 <2-6>, 因此我们要将从tracker获取的值增加(+2):int operation = morph_operator + 2;
void Threshold_Demo( int, void* )
{
/* 0: 二进制阈值
1: 反二进制阈值
2: 截断阈值
3: 0阈值
4: 反0阈值
*/
threshold( src_gray, dst, threshold_value, max_BINARY_value,threshold_type );
imshow( window_name, dst );
}
就像你看到的那样,在这样的过程中,函数 threshold<> 会接受到5个参数:
filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
其中各参数含义如下:
填充图像边界的两种方法:
copyMakeBorder( src, dst, top, bottom, left, right, borderType, value );
接受参数:
为什么对图像进行求导是重要的呢? 假设我们需要检测图像中的 边缘 ,如下图:
你可以看到在 边缘 ,相素值显著的 改变 了。表示这一 改变 的一个方法是使用 导数 。 梯度值的大变预示着图像中内容的显著变化。
用更加形象的图像来解释,假设我们有一张一维图形。下图中灰度值的”跃升”表示边缘的存在:
使用一阶微分求导我们可以更加清晰的看到边缘”跃升”的存在(这里显示为高峰值)
从上例中我们可以推论检测边缘可以通过定位梯度值大于邻域的相素的方法找到(或者推广到大于一个阀值).
a.首先申明变量:
Mat src, src_gray;
Mat grad;
char* window_name = "Sobel Demo - Simple Edge Detector";
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
b.装载原图像 src:
src = imread( argv[1] );
if( !src.data )
{ return -1; }
c.第一步对原图像使用 GaussianBlur 降噪 ( 内核大小 = 3 )
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
d.将降噪后的图像转换为灰度图:
cvtColor( src, src_gray, CV_RGB2GRAY );
e.第二步,在 x 和 y 方向分别”求导“。 为此,我们使用函数 Sobel :
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
/// 求 X方向梯度
Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
/// 求 Y方向梯度
Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
该函数接受了以下参数:
注意为了在 x 方向求导我们使用: ,
. 采用同样方法在 y 方向求导。
f.将中间结果转换到 CV_8U:
convertScaleAbs( grad_x, abs_grad_x );
convertScaleAbs( grad_y, abs_grad_y );
g.将两个方向的梯度相加来求取近似 梯度 (注意这里没有准确的计算,但是对我们来讲已经足够了)。
addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
h.最后,显示结果:
imshow( window_name, grad );
前一节我们学习了 Sobel 算子 ,其基础来自于一个事实,即在边缘部分,像素值出现”跳跃“或者较大的变化。如果在此边缘部分求取一阶导数,你会看到极值的出现。正如下图所示:
如果在边缘部分求二阶导数会出现什么情况?
你会发现在一阶导数的极值位置,二阶导数为0。所以我们也可以用这个特点来作为检测图像边缘的方法。 但是, 二阶导数的0值不仅仅出现在边缘(它们也可能出现在无意义的位置),但是我们可以过滤掉这些点。
过程
对灰度图使用Laplacian算子:
Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );
函数接受了以下参数:
- src_gray: 输入图像。- dst: 输出图像- ddepth: 输出图像的深度。 因为输入图像的深度是 CV_8U ,这里我们必须定义 ddepth = CV_16S 以避免外溢。- kernel_size: 内部调用的 Sobel算子的内核大小,此例中设置为3。- scale, delta 和 BORDER_DEFAULT: 使用默认值。
Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
输入参数:
#include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <stdlib.h> #include <stdio.h> using namespace cv; /// 全局变量 Mat src, src_gray; Mat dst, detected_edges; int edgeThresh = 1; int lowThreshold; int const max_lowThreshold = 100; int ratio = 3; int kernel_size = 3; char* window_name = "Edge Map"; /** * @函数 CannyThreshold * @简介: trackbar 交互回调 - Canny阈值输入比例1:3 */ void CannyThreshold(int, void*) { /// 使用 3x3内核降噪 blur( src_gray, detected_edges, Size(3,3) ); /// 运行Canny算子 Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size ); /// 使用 Canny算子输出边缘作为掩码显示原图像 dst = Scalar::all(0); src.copyTo( dst, detected_edges); imshow( window_name, dst ); } /** @函数 main */ int main( int argc, char** argv ) { /// 装载图像 src = imread( argv[1] ); if( !src.data ) { return -1; } /// 创建与src同类型和大小的矩阵(dst) dst.create( src.size(), src.type() ); /// 原图像转换为灰度图像 cvtColor( src, src_gray, CV_BGR2GRAY ); /// 创建显示窗口 namedWindow( window_name, CV_WINDOW_AUTOSIZE ); /// 创建trackbar createTrackbar( "Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold ); /// 显示图像 CannyThreshold(0, 0); /// 等待用户反应 waitKey(0); return 0; }