OpenCV学习笔记__入门篇(二)

1、图像平滑处理

使用各种线性滤波器对图像进行平滑(模糊)处理,相关OpenCV函数如下:

    blur                              归一化块滤波器

    -  GaussianBlur               高斯滤波器
    -  medianBlur                 中值滤波器
    -  bilateralFilter               双边滤波器

2、腐蚀与膨胀( Erosion 与 Dilation)
OpenCV提供的两种最基本的形态学操作:
    -  erode
    -  dilate

     腐蚀:亮区变细      膨胀:亮区扩张   
     一些涉及到的函数:  createTrackbar( 滚动条名称,窗口名称 ,位置 ,滚动条最大值 ,调用的函数名 )
                                       Mat element = getStructuringElement(内核类型,内核大小,锚点位置);

3、更多形态学操作(基于erosion与dilation) 使用OpenCV函数  morphologyEx  进行形态学操作:
    - 开运算 (Opening)
    - 闭运算 (Closing)
    - 形态梯度 (Morphological Gradient)
    - 顶帽 (Top Hat)
    - 黑帽(Black Hat)

运行形态学操作的核心函数是 morphologyEx 。                morphologyEx (   src ,   dst ,   operation ,   element   );

在本例中,我们使用了4个参数(其余使用默认值):

    -  src : 原 (输入) 图像
    -  dst: 输出图像
    -  operation: 需要运行的形态学操作。 我们有5个选项:
        -  Opening: MORPH_OPEN : 2
        -  Closing: MORPH_CLOSE: 3
        -  Gradient: MORPH_GRADIENT: 4
        -  Top Hat: MORPH_TOPHAT: 5
        -  Black Hat: MORPH_BLACKHAT: 6

它们的取值范围是 <2-6>, 因此我们要将从tracker获取的值增加(+2):int operation = morph_operator + 2;

    -  element: 内核,可以使用函数:get_structuring_element: getStructuringElement <> 自定义。


4、图像金字塔
使用OpenCV函数  pyrUp    pyrDown  对图像进行向上和向下采样,即放大、缩小。
note:我们向下采样缩小图像的时候, 我们实际上  丢失  了一些信息。

pyrUp(tmp, dst, Size(tmp.cols * 2, tmp.rows * 2));
pyrDown(tmp, dst, Size(tmp.cols / 2, tmp.rows / 2));

勿漏:tmp = dst;           将输入图像  tmp  更新为当前显示图像, 这样后续操作将作用于更新后的图像。


5、 基本的阈值操作          OpenCV中的阈值(threshold)函数:  threshold  的运用。
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个参数:

    -  src_gray: 输入的灰度图像的地址。
    -  dst: 输出图像的地址。
    -  threshold_value: 进行阈值操作时阈值的大小。
    -  max_BINARY_value: 设定的最大灰度值(该参数运用在二进制与反二进制阈值操作中)。
    -  threshold_type: 阈值的类型。从上面提到的5种中选择出的结果。


6、 实现自己的线性滤波器 用OpenCV函数  filter2D  创建自己的线性滤波器。(注意一些关键词)
filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
其中各参数含义如下:
      src: 源图像
      dst: 目标图像
      ddepthdst 的深度。若为负值(如-1),则表示其深度与源图像相等。
      kernel: 用来遍历图像的核
      anchor: 核的锚点的相对位置,其中心点默认为  (-1, -1) 。
      delta: 在卷积过程中,该值会加到每个像素上。默认情况下,这个值为 0。
      BORDER_DEFAULT: 这里我们保持其默认值,更多细节将在其他教程中详解。


7、 给图像添加边界 使用OpenCV函数  copyMakeBorder  设置边界(添加额外的边界)。

填充图像边界的两种方法:

       BORDER_CONSTANT: 使用常数填充边界 (i.e. 黑色或者  0)
       BORDER_REPLICATE: 复制原图中最临近的行或者列。
copyMakeBorder( src, dst, top, bottom, left, right, borderType, value );

接受参数:

       src: 原图像
       dst: 目标图像
       topbottomleftright: 各边界的宽度,此处定义为原图像尺寸的5%。
       borderType: 边界类型,此处可以选择常数边界或者复制边界。
       value: 如果  borderType 类型是  BORDER_CONSTANT, 该值用来填充边界像素。


8、Sobel 导数
    - 使用OpenCV函数  Sobel 对图像求导。
    - 使用OpenCV函数  Scharr 更准确地计算  3 \times 3 核的导数。

为什么对图像进行求导是重要的呢? 假设我们需要检测图像中的 边缘 ,如下图:

How intensity changes in an edge

你可以看到在 边缘 ,相素值显著的 改变 了。表示这一 改变 的一个方法是使用 导数 。 梯度值的大变预示着图像中内容的显著变化。

用更加形象的图像来解释,假设我们有一张一维图形。下图中灰度值的”跃升”表示边缘的存在:

Intensity Plot for an edge

使用一阶微分求导我们可以更加清晰的看到边缘”跃升”的存在(这里显示为高峰值)

First derivative of Intensity - Plot for an edge
从上例中我们可以推论检测边缘可以通过定位梯度值大于邻域的相素的方法找到(或者推广到大于一个阀值).

计算:在两个方向求导:水平变化、垂直变化

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 );

该函数接受了以下参数:

        -  src_gray: 在本例中为输入图像,元素类型  CV_8U
        -  grad_x/ grad_y: 输出图像.
        -  ddepth: 输出图像的深度,设定为  CV_16S 避免外溢。
        -  x_orderx 方向求导的阶数。
        -  y_ordery 方向求导的阶数。
        -  scaledelta 和  BORDER_DEFAULT: 使用默认值

注意为了在 x 方向求导我们使用: x_{order}= 1 , y_{order} = 0. 采用同样方法在 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 );


9、Laplace 算子

前一节我们学习了 Sobel 算子 ,其基础来自于一个事实,即在边缘部分,像素值出现”跳跃“或者较大的变化。如果在此边缘部分求取一阶导数,你会看到极值的出现。正如下图所示:

Previous theory

如果在边缘部分求二阶导数会出现什么情况?

Second derivative

你会发现在一阶导数的极值位置,二阶导数为0。所以我们也可以用这个特点来作为检测图像边缘的方法。 但是, 二阶导数的0值不仅仅出现在边缘(它们也可能出现在无意义的位置),但是我们可以过滤掉这些点。


过程

    - 装载图像
    - 使用高斯平滑消除噪声, 将图像转换到灰度空间。
    - 使用Laplacian算子作用于灰度图像,并保存输出图像。
    - 输出结果。

对灰度图使用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。
    - scaledelta 和 BORDER_DEFAULT: 使用默认值。


10、Canny 边缘检测算法
被很多人认为是边缘检测的  最优算法 , 最优边缘检测的三个主要评价标准是:
    -  低错误率: 标识出尽可能多的实际边缘,同时尽可能的减少噪声产生的误报。
    -  高定位性: 标识出的边缘要与图像中的实际边缘尽可能接近。
    -  最小响应: 图像中的边缘只能标识一次。

Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );

输入参数:

    -  detected_edges: 原灰度图像
    -  detected_edges: 输出图像 (支持原地计算,可为输入图像)
    -  lowThreshold: 用户通过 trackbar设定的值。
    -  highThreshold: 设定为低阈值的3倍 (根据Canny算法的推荐)
    -  kernel_size: 设定为 3 (Sobel内核大小,内部使用)
#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;
  }


 
 








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值