opencv 基础

Mat  基本图像容器

      Mat是一个类,由两个数据组成:矩阵头(包含矩阵的尺寸,存储方法,存储地址等),指向存储所有像素值矩阵的指针。

      OpenCV使用引用计数机制。其思路是让每个 Mat 对象有自己的信息头,但共享同一个矩阵。这通过让矩阵指针指向同一地址而实现。而拷贝构造函数则 只拷贝信息头和矩阵指针 ,而不拷贝矩阵。

Mat A, C;                                 // 只创建信息头部分
A = imread(argv[1], CV_LOAD_IMAGE_COLOR); // 这里为矩阵开辟内存

Mat B(A);                                 // 使用拷贝构造函数

C = A;                                    // 赋值运算符

      以上代码中的所有Mat对象最终都指向同一个也是唯一一个数据矩阵。虽然它们的信息头不同,但通过任何一个对象所做的改变也会影响其它对象。实际上,不同的对象只是访问相同数据的不同途径而已。

     拷贝矩阵本身(不只是信息头和矩阵指针),这时可以使用函数 clone() 或者 copyTo() 。

Mat F = A.clone();
Mat M(2,2, CV_8UC3, Scalar(0,0,255)); 
it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it


Mat G;A.copyTo(G);

      现在改变 F 或者 G 就不会影响 Mat 信息头所指向的矩阵。

总结

      OpenCV函数中输出图像的内存分配是自动完成的(如果不特别指定的话)。
      使用OpenCV的C++接口时不需要考虑内存释放问题。
      赋值运算符和拷贝构造函数( ctor )只拷贝信息头。

      使用函数 clone() 或者 copyTo() 来拷贝一副图像的矩阵。

Mat构造函数

Mat M(2,2, CV_8UC3, Scalar(0,0,255)); 

在 C\C++ 中通过构造函数进行初始化

    int sz[3] = {2,2,2}; 
    Mat L(3,sz, CV_8UC(1), Scalar::all(0));
Create() function: 函数
M.create(4,4, CV_8UC(2));

这个创建方法不能为矩阵设初值,它只是在改变尺寸时重新为矩阵数据开辟内存。

opencv中像素的遍历和存储

opencv中RGB颜色模型的矩阵:

\newcommand{\tabIt}[1] { \textcolor{yellow}{#1} \cellcolor{blue} &  \textcolor{black}{#1} \cellcolor{green} & \textcolor{black}{#1} \cellcolor{red}}\begin{tabular} {ccccccccccccc}~ & \multicolumn{3}{c}{Column 0} &   \multicolumn{3}{c}{Column 1} &   \multicolumn{3}{c}{Column ...} & \multicolumn{3}{c}{Column m}\\Row 0 & \tabIt{0,0} & \tabIt{0,1} & \tabIt{...}  & \tabIt{0, m} \\Row 1 & \tabIt{1,0} & \tabIt{1,1} & \tabIt{...}  & \tabIt{1, m} \\Row ... & \tabIt{...,0} & \tabIt{...,1} & \tabIt{...} & \tabIt{..., m} \\Row n & \tabIt{n,0} & \tabIt{n,1} & \tabIt{n,...} & \tabIt{n, m} \\\end{tabular}

遍历图像的方法

     指针

p = I.ptr<uchar>(i);

    data   data会从 Mat 中返回指向矩阵第一行第一列的指针。

uchar* p = I.data;

for( unsigned int i =0; i < ncol*nrows; ++i)
    *p++ = table[*p];

迭代器

       迭代法则被认为是一种以更安全的方式来实现这一功能。在迭代法中,你所需要做的仅仅是获得图像矩阵的begin和end,然后增加迭代直至从begin到end。将*操作符添加在迭代指针前,即可访问当前指向的内容。

单通道

 for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
                *it = table[*it];

三通道

     for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)
            {
                (*it)[0] = table[(*it)[0]];
                (*it)[1] = table[(*it)[1]];
                (*it)[2] = table[(*it)[2]];
            }

利用at()函数

  for( int i = 0; i < I.rows; ++i)
                for( int j = 0; j < I.cols; ++j )
                    I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];
核心函数LUT(The Core Function)

     这是最被推荐的用于实现批量图像元素查找和更该操作图像方法。在图像处理中,对于一个给定的值,将其替换成其他的值是一个很常见的操作,OpenCV 提供里一个函数直接实现该操作,并不需要你自己扫描图像,就是:operationsOnArrays:LUT() <lut> ,一个包含于core module的函数. 首先我们建立一个mat型用于查表:

    Mat lookUpTable(1, 256, CV_8U);
    uchar* p = lookUpTable.data; 
    for( int i = 0; i < 256; ++i)
        p[i] = table[i];

然后我们调用函数 (I 是输入 J 是输出):

        LUT(I, lookUpTable, J);

矩阵的掩码操作

        其思想是:根据掩码矩阵(也称作核)重新计算图像中每个像素的值。掩码矩阵中的值表示近邻像素值(包括该像素自身的值)对新像素值有多大影响。从数学观点看,我们用自己设置的权值,对像素邻域内的值做了个加权平均。

像素基本方法

    for(int j = 1 ; j < myImage.rows-1; ++j)
    {
        const uchar* previous = myImage.ptr<uchar>(j - 1);
        const uchar* current  = myImage.ptr<uchar>(j    );
        const uchar* next     = myImage.ptr<uchar>(j + 1);

        uchar* output = Result.ptr<uchar>(j);

        for(int i= nChannels;i < nChannels*(myImage.cols-1); ++i)
        {
            *output++ = saturate_cast<uchar>(5*current[i]
                         -current[i-nChannels] - current[i+nChannels] - previous[i] - next[i]);
        }
    }

filter2D函数 自定义滤波器,自定义滤波器的核。

滤波器在图像处理中的应用太广泛了,因此OpenCV也有个用到了滤波器掩码(某些场合也称作核)的函数。不过想使用这个函数,你必须先定义一个表示掩码的 Mat 对象:

Mat kern = (Mat_<char>(3,3) <<  0, -1,  0,
                               -1,  5, -1,
                                0, -1,  0);

然后调用 filter2D 函数,参数包括输入、输出图像以及用到的核:

filter2D(I, K, I.depth(), kern );

图像处理的基本操作

(1)图像的平滑处理

       归一化快滤波器

OpenCV函数 blur 执行了归一化块平滑操作。

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

我们输入4个实参 (详细的解释请参考 Reference):

  • src: 输入图像
  • dst: 输出图像
  • Size( w,h ): 定义内核大小( w 像素宽度, h 像素高度)
  • Point(-1, -1): 指定锚点位置(被平滑点), 如果是负值,取核的中心为锚点。

       高斯滤波器

OpenCV函数 GaussianBlur 执行高斯平滑 :

  1. 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; } }
    

我们输入4个实参 (详细的解释请参考 Reference):

  • src: 输入图像
  • dst: 输出图像
  • Size(w, h): 定义内核的大小(需要考虑的邻域范围)。 w 和 h 必须是正奇数,否则将使用 \sigma_{x} 和 \sigma_{y} 参数来计算内核大小。
  • \sigma_{x}: x 方向标准方差, 如果是 0 则 \sigma_{x} 使用内核大小计算得到。
  • \sigma_{y}: y 方向标准方差, 如果是 0 则 \sigma_{y} 使用内核大小计算得到。.

       中值滤波器

OpenCV函数 medianBlur 执行中值滤波操作:

for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
    { medianBlur ( src, dst, i );
      if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }

我们用了3个参数:

  • src: 输入图像
  • dst: 输出图像, 必须与 src 相同类型
  • i: 内核大小 (只需一个值,因为我们使用正方形窗口),必须为奇数。

      双边滤波器

       目前我们了解的滤波器都是为了 平滑 图像, 问题是有些时候这些滤波器不仅仅削弱了噪声, 连带着把边缘也给磨掉了。 为避免这样的情形 (至少在一定程度上 ), 我们可以使用双边滤波。
       类似于高斯滤波器,双边滤波器也给每一个邻域像素分配一个加权系数。 这些加权系数包含两个部分, 第一部分加权方式与高斯滤波一样,第二部分的权重则取决于该邻域像素与当前像素的灰度差值。

OpenCV函数 bilateralFilter 执行双边滤波操作:

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

我们使用了5个参数:

  • src: 输入图像
  • dst: 输出图像
  • d: 像素的邻域直径
  • \sigma_{Color}: 颜色空间的标准方差
  • \sigma_{Space}: 坐标空间的标准方差(像素单位)

基本形态学 腐蚀Erosion和膨胀Dilation

膨胀

进行膨胀操作时,将内核 B 划过图像,将内核 B 覆盖区域的最大相素值提取,并代替锚点位置的相素。

void Dilation( int, void* )
{
  int dilation_type;
  if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; }
  else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; }
  else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }

  Mat element = getStructuringElement( dilation_type,
                                       Size( 2*dilation_size + 1, 2*dilation_size+1 ),
                                       Point( dilation_size, dilation_size ) );
  /// 膨胀操作
  dilate( src, dilation_dst, element );
  imshow( "Dilation Demo", dilation_dst );


腐蚀

进行腐蚀操作时,将内核 B 划过图像,将内核 B 覆盖区域的最小相素值提取,并代替锚点位置的相素。

   

void Erosion( int, void* )
{
  int erosion_type;
  if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; }
  else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; }
  else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; }

  Mat element = getStructuringElement( erosion_type,
                                       Size( 2*erosion_size + 1, 2*erosion_size+1 ),
                                       Point( erosion_size, erosion_size ) );
  /// 腐蚀操作
  erode( src, erosion_dst, element );
  imshow( "Erosion Demo", erosion_dst );
}
  • 进行 腐蚀 操作的函数是 erode 。 它接受了三个参数:

    • src: 原图像

    • erosion_dst: 输出图像

    • element: 腐蚀操作的内核。 如果不指定,默认为一个简单的 3x3 矩阵。否则,我们就要明确指定它的形状,可以使用函数 getStructuringElement:

      Mat element = getStructuringElement( erosion_type,
                                           Size( 2*erosion_size + 1, 2*erosion_size+1 ),
                                           Point( erosion_size, erosion_size ) );
      

    我们可以为我们的内核选择三种形状之一:

    • 矩形: MORPH_RECT
    • 交叉形: MORPH_CROSS
    • 椭圆形: MORPH_ELLIPSE

    然后,我们还需要指定内核大小,以及 锚点 位置。不指定锚点位置,则默认锚点在内核中心位置。

 常用形态学操作

     开运算 Opening

  • 开运算是通过先对图像腐蚀再膨胀实现的。

    dst = open( src, element) = dilate( erode( src, element ) )

  • 能够排除小团块物体(假设物体较背景明亮)

  • Opening


    闭运算 Closing

    • 闭运算是通过先对图像膨胀再腐蚀实现的。

      dst = close( src, element ) = erode( dilate( src, element ) )

    • 能够排除小型黑洞(黑色区域)。

      Closing example

    形态梯度 Morphological Gradient

  • 膨胀图与腐蚀图之差

    dst = morph_{grad}( src, element ) = dilate( src, element ) - erode( src, element )

  • 能够保留物体的边缘轮廓,如下所示:

    Gradient

    顶帽 TopHat


  原图像与开运算结果图之差

dst = tophat( src, element ) = src - open( src, element )

Top Hat

    黑帽 Black Hat

  闭运算结果图与原图像之差

dst = blackhat( src, element ) = close( src, element ) - src

Black Hat

  基本的阈值操作

  • OpenCV中提供了阈值(threshold)函数: threshold 。

  • 这个函数有5种阈值化类型


  



阅读更多 登录后自动展开
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页