【数字图像处理】OpenCV3 学习笔记

1. 加载保存显示图像

Mat imread( const String& filename, int flags = IMREAD_COLOR );

bool imwrite( const String& filename, InputArray img,
              const std::vector<int>& params = std::vector<int>());

void cvtColor( InputArray src, OutputArray dst, int code, int dstCn = 0 );

2. 操作图像像素

2.1. 方法1:使用 Mat 中对矩阵元素的地址定位的知识

step[0]:一行字节数
step[1]:一个像素字节数

    int main()
    {
        //新建一个uchar类型的单通道矩阵(grayscale image 灰度图)
        Mat m(400, 400, CV_8U, Scalar(0));
        for (int col = 0; col < 400; col++)
        {
            for (int row = 195; row < 205; row++)
            {
                cout << (int)(*(m.data + m.step[0] * row + m.step[1] * col)) << "  ==> ";
                //获取第[row,col]个像素点的地址并用 * 符号解析
                *(m.data + m.step[0] * row + m.step[1] * col) = 255;
                cout << (int)(*(m.data + m.step[0] * row + m.step[1] * col)) << endl;
            }
        }
        imshow("canvas", m);
        cvWaitKey();
        return 0;
    }

2.2. 方法二:使用 Mat::at 函数

    int main()
    {    
        Mat img = imread("lena.jpg");
        imshow("Lena Original", img);
        for (int row = 0; row < img.rows; row++)
        {
            for (int col = 0; col < img.cols; col++)
            {    
                /* 注意 Mat::at 函数是个模板函数, 需要指明参数类型, 因为这张图是具有红蓝绿三通道的图,
                   所以它的参数类型可以传递一个 Vec3b, 这是一个存放 3 个 uchar 数据的 Vec(向量). 这里
                   提供了索引重载, [2]表示的是返回第三个通道, 在这里是 Red 通道, 第一个通道(Blue)用[0]返回 */
                if(img.at<Vec3b>(row, col)[2] > 128)
                    img.at<Vec3b>(row, col) = Vec3b(255, 255, 255);
            }
        }
        imshow("Lena Modified", img);
        cvWaitKey();
        return 0;
    }

2.3. 方法三:使用 Mat 的一个模板子类 Mat_

    int main()
    {    
        Mat m(400, 400, CV_8UC3, Scalar(255, 255, 255));
        // m2 是 Mat_<Vec3b> 类型的, 因为 m 中元素的类型是 CV_8UC3, 可以用 Vec3b 存储 3 个通道的值
        // 注意 Mat_<CV_8UC3> 这种写法是错误的, 因为 CV_8UC3 只是一个宏定义
        // #define CV_8UC3 CV_MAKETYPE(CV_8U, 3)
        Mat_<Vec3b> m2 = m;
        // for 循环画一个红色的实心圆
        for (int y = 0; y < m.rows; y++)
        {
            for (int x = 0; x < m.rows; x++)
            {
                if (pow(double(x-200), 2) + pow(double(y-200), 2) - 10000.0 < 0.00000000001)
                {
                    // Mat_ 模板类实现了对()的重载, 可以定位到一个像素
                    m2(x, y) = Vec3b(0, 0, 255);
                }
            }
        }
        imshow("Image", m);
        cvWaitKey();
        return 0;
    }

2.4. 方法四:使用 Mat::ptr 模板函数

    int main()
    {    
        Mat m(400, 400, CV_8UC3, Scalar(226, 46, 166));
        imshow("Before", m);
        for (int row = 0; row < m.rows; row++)
        {
            if (row % 5 == 0)
            {
                // data 是 uchar* 类型的, m.ptr<uchar>(row) 返回第 row 行数据的首地址
                // 需要注意的是该行数据是按顺序存放的,也就是对于一个 3 通道的 Mat, 一个像素有
                // 有 3 个通道值, [B,G,R][B,G,R][B,G,R]... 所以一行长度为:
                // sizeof(uchar) * m.cols * m.channels() 个字节
                uchar* data = m.ptr<uchar>(row);
                for (int col = 0; col < m.cols; col++)
                {
                    data[col * 3] = 102; //第row行的第col个像素点的第一个通道值 Blue
                    data[col * 3 + 1] = 217; // Green
                    data[col * 3 + 2] = 239; // Red
                }
            }
        }
        imshow("After", m);
        cout << (int)m.at<Vec3b>(0, 0)[0] << ','; //利用 Fn 1 介绍的方法输出一下像素值到控制台
        cout << (int)m.at<Vec3b>(0, 0)[1] << ',';
        cout << (int)m.at<Vec3b>(0, 0)[2] << endl;
        cvWaitKey();
        return 0;

    }

3. 图像掩模

/*
 * @Descripttion: 
 * @version: 
 * @Author: Yueyang
 * @email: 1700695611@qq.com
 * @Date: 2021-05-02 23:45:24
 * @LastEditors: Yueyang
 * @LastEditTime: 2021-05-03 00:50:40
 */
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
using namespace std;
int main()
{
    Mat srcImage,dstImage;
    srcImage=imread("F:/SDK/OpenCV/HiKivision/picture/lena.jpg");
    //if(!srcImage.data){cout<<"load error"<<endl;}
    // int cols=(srcImage.cols-1)*srcImage.channels();
    // int rows=srcImage.rows;
    // int offset=srcImage.channels();
    // dstImage=Mat::zeros(srcImage.size(),srcImage.type());
    // for(int row=1;row<rows-1;row++)
    // {
    //     const uchar* priviousptr=srcImage.ptr<uchar>(row-1);
    //     const uchar* currentptr =srcImage.ptr<uchar>(row);
    //     const uchar* nextptr    =srcImage.ptr<uchar>(row+1);
    //     uchar* outptr     =dstImage.ptr<uchar>(row);
    //     /**
    //      * 相当于卷积
    //      *    -1
    //      * -1  5  -1
    //      *    -1
    //     */
    //     for(int col=offset;col<cols;col++)
    //     {
    //         outptr[col]=saturate_cast<uchar>( 5*currentptr[col]-currentptr[col-offset]-currentptr[col+offset]-priviousptr[col]-nextptr[col]);
    //     }
    // }

    Mat kernel =(Mat_<char>(3,3)<<0,-1,0,-1,5,-1,0,-1,0);
    filter2D(srcImage,dstImage,srcImage.depth(),kernel);
    
    imwrite("F:/SDK/OpenCV/HiKivision/picture/mask.jpg",dstImage);
    return 0;
}

4. 图像线性变换

4.1. 图像线性混合

g(x,y)=af1(x,y)+bf2(x,y)+gamma

void addWeighted(InputArray src1, double alpha, InputArray src2,
                              double beta, double gamma, OutputArray dst, int dtype = -1);
/*
 * @Descripttion: 
 * @version: 
 * @Author: Yueyang
 * @email: 1700695611@qq.com
 * @Date: 2021-05-03 01:24:41
 * @LastEditors: Yueyang
 * @LastEditTime: 2021-05-03 01:35:19
 */
#include <opencv2/opencv.hpp>
using namespace cv;
int main()
{
    Mat srcImage1,srcImage2,dstImage;
    srcImage1=imread("F:/SDK/OpenCV/HiKivision/picture/lena.jpg");
    srcImage2=imread("F:/SDK/OpenCV/HiKivision/picture/apple.jpg");

    addWeighted(srcImage1,0.5,srcImage2,0.5,0,dstImage);
    imwrite("F:/SDK/OpenCV/HiKivision/picture/aadd.jpg",dstImage);
    multiply(srcImage1,srcImage1,dstImage,0.5);
    imwrite("F:/SDK/OpenCV/HiKivision/picture/muliy.jpg",dstImage);

    return 0;
}

4.2. 图像亮度与对比度

g(x,y)=a*f(x,y)+b
上面这个公式可以很好的解释对图像的亮度和对比度操作的原理,第一个参数α必须是大于零,不然则基本上没有意义了。
α能代表什么呢?α能使图像像素成倍数的增长或降低(α<1),改变了是图像的对比度,因为使图像的差值变化了。
那么β作何解释呢?β可为负,也可为正,那么任何一个像素都在(0, 255)之间,加上一个值或减去一个值则会使这个
像素点变大或变小,其实就是向白色或向黑色靠近(0为黑,255为白),所以改变的是图像的亮度。

/*
 * @Descripttion: 
 * @version: 
 * @Author: Yueyang
 * @email: 1700695611@qq.com
 * @Date: 2021-05-03 01:42:42
 * @LastEditors: Yueyang
 * @LastEditTime: 2021-05-03 08:54:29
 */
#include <opencv2/opencv.hpp>
using namespace cv;
int main()
{
    Mat srcImage,dstImage;
    srcImage=imread("F:/SDK/OpenCV/HiKivision/picture/lena.jpg");
    dstImage=Mat::zeros(srcImage.size(),srcImage.type());
    int height =srcImage.rows;
    int width=srcImage.cols;
    //1.2*pix+30
    float alpha=1.2;
    float beta=50;
    
    for(int row=0;row<width;row++)
    {
        for(int col=0;col<width;col++)
        {
            if(srcImage.channels()==3){
                float b=srcImage.at<Vec3b>(row,col)[0];
                float g=srcImage.at<Vec3b>(row,col)[1];
                float r=srcImage.at<Vec3b>(row,col)[2];
                dstImage.at<Vec3b>(row,col)[0]=saturate_cast<uchar> (b*alpha+beta);
                dstImage.at<Vec3b>(row,col)[1]=saturate_cast<uchar> (g*alpha+beta);
                dstImage.at<Vec3b>(row,col)[2]=saturate_cast<uchar> (r*alpha+beta);
            }else if(srcImage.channels()==4){
                float b=srcImage.at<Vec4b>(row,col)[0];
                float g=srcImage.at<Vec4b>(row,col)[1];
                float r=srcImage.at<Vec4b>(row,col)[2];
                dstImage.at<Vec4b>(row,col)[0]=saturate_cast<uchar> (b*alpha+beta);
                dstImage.at<Vec4b>(row,col)[1]=saturate_cast<uchar> (g*alpha+beta);
                dstImage.at<Vec4b>(row,col)[2]=saturate_cast<uchar> (r*alpha+beta);
                dstImage.at<Vec4b>(row,col)[3]=srcImage.at<Vec4b>(row,col)[3];
            }else{
                float b=(float)srcImage.at<uchar>(row,col);
                dstImage.at<Vec3b>(row,col)=b*alpha+beta;        
            }
        }
    }
    imwrite("F:/SDK/OpenCV/HiKivision/picture/linghtchange.jpg",dstImage);
    return 0;
}

4.3. 线性模糊

4.3.1. 均值模糊

void blur( InputArray src, OutputArray dst,
Size ksize, Point anchor = Point(-1,-1),
int borderType = BORDER_DEFAULT );

4.3.2. 高斯模糊

void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT )

InputArray src:输入的图像
OutputArray dst:输出图像
Size ksize:高斯卷积核的大小,是奇数
double sigmaX, double sigmaY=0, :表示x和y方向的方 差,如果y=0则y方向的方差与x相等
int borderType=BORDER_DEFAULT :边界的处理方式,一般默认

高斯模糊:越靠近卷积核的领域权重越大。
均值模糊:领域权重都为1。
而无论是高斯模糊或者是均值模糊,有一个缺点是他们在模糊的时候并不能很好的保留
边缘信息。因此双边滤波便很好客服了这一缺陷。原理如图:

void bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT )
InputArray src: 输入图像,可以是Mat类型,图像必须是8位或浮点型单通道、三通道的图像。
. OutputArray dst: 输出图像,和原图像有相同的尺寸和类型。
. int d: 表示在过滤过程中每个像素邻域的直径范围。如果这个值是非正数,则函数会从第五个参数sigmaSpace计算该值。
. double sigmaColor: 颜色空间过滤器的sigma值,这个参数的值月大,表明该像素邻域内有月宽广的颜色会被混合到一起,产生较大的半相等颜色区域。
. double sigmaSpace: 坐标空间中滤波器的sigma值,如果该值较大,则意味着颜色相近的较远的像素将相互影响,从而使更大的区域中足够相似的颜色获取相同的颜色。当d>0时,d指定了邻域大小且与sigmaSpace五官,否则d正比于sigmaSpace.
. int borderType=BORDER_DEFAULT: 用于推断图像外部像素的某种边界模式,有默认值BORDER_DEFAULT.

5. 形态学操作

形态学元素
Mat getStructuringElement(int shape, Size ksize, Point anchor = Point(-1,-1));

5.1. 腐蚀膨胀

膨胀
void dilate( InputArray src, OutputArray dst, InputArray kernel,
Point anchor = Point(-1,-1), int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar& borderValue = morphologyDefaultBorderValue() );
腐蚀
void erode( InputArray src, OutputArray dst, InputArray kernel,
Point anchor = Point(-1,-1), int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar& borderValue = morphologyDefaultBorderValue() );

跟卷积操作类似,假设有图像A和结构元素B,结构元素B在A上面移动,其中B定义其中心为锚点,计算B覆盖下A的最大像素值用来替换锚点的像素,其中B作为结构体可以是任意形状。我们回忆一下中值平滑操作——取每一个位置的矩形领域内值的中值作为该位置的输出灰度值,图像的膨胀操作与中值平滑操作类似,它是取每一个位置的矩形领域内值的最大值作为该位置的输出灰度值。不同的是,这里的领域不再单纯是矩形结构的,也可以是椭圆形结构的、十字交叉形结构的等,其中红色是参考点,也称为锚点(anchor point),如下所示:

因此取每个位置领域内最大值,所以膨胀后输出图像的总体亮度的平均值比起原图会有所升高,图像中比较亮的区域的面积会变大,而较暗物体的尺寸会减小甚至消失

腐蚀操作与膨胀操作类似,只是它取结构元所指定的领域内值的最小值作为该位置的输出灰度值。因为取每个位置领域内最小值,所以腐蚀后输出图像的总体亮度的平均值比起原图会有所降低,图像中比较亮的区域的面积会变小甚至消失,而较暗物体的尺寸会扩大

5.2. 开闭操作

cv2.morphologyEX(
img 输入图像
cv2.MORPH_CLOSE,cv2.MORPH_OPEN 形态学操作
kernel

用于对二值化后的图像进行处理,属于形态学操作(morphology)
开操作:消除白色的小点,去除小的干扰块
先对图像腐蚀后膨胀

闭操作:消除黑色的小块,填充闭合区域
先对图像膨胀后腐蚀

5.3. 顶帽黑帽

顶帽
就是开运算去掉的亮点
又称礼帽,是原始图像与进行开运算之后得到的图像的差。

因为开运算到来的结果是放大了裂痕或者局部低亮度的区域,因此,从原图中减去运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关。
顶帽运算往往用来分离比邻近点亮一些的斑块。当一幅图像具有大幅的背景的时候,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取

黑帽
是进行闭运算以后得到的图像与原图像的差。
就是闭运算去掉的黑点
黑帽运算之后的效果图突出了与原图像轮廓周围的区域更暗的区域,且这一操作和选择的核大小相关。所以黑帽运算用来分离比邻近点暗一些的斑块。

5.4. 形态学梯度

形态学梯度是膨胀和腐蚀的差别,结果看上去就像前景物体的轮廓。计算的梯度常见的有三种:

基本梯度:
基本梯度是用膨胀后的图像减去腐蚀后的图像得到差值图像,称为梯度图像,也是OpenCV中支持的计算形态学梯度的方法,而此方法得到的梯度又被称为基本梯度。

内部梯度:
是用原图像减去腐蚀后的图像得到差值图像,称为图像的内部梯度

外部梯度:
图像膨胀后的图像减去原来的图像得到的差值图像,称为图像的外部梯度。

6. 图像金字塔

一个图像金字塔是一系列图像的集合 - 所有图像来源于同一张原始图像 - 通过梯次向下采样获得,直到达到某个终止条件才停止采样。
有两种类型的图像金字塔常常出现在文献和应用中:
高斯金字塔(Gaussian pyramid): 用来向下采样
拉普拉斯金字塔(Laplacian pyramid): 用来从金字塔低层图像重建上层未采样图像

6.1. 高斯金字塔

想想金字塔为一层一层的图像,层级越高,图像越小。

每一层都按从下到上的次序编号, 层级 (i+1) (表示为 G_{i+1} 尺寸小于层级 i (G_{i}))。

为了获取层级为 (i+1) 的金字塔图像,我们采用如下方法:

将 G_{i} 与高斯内核卷积:

将所有偶数行和列去除。

显而易见,结果图像只有原图的四分之一。通过对输入图像 G_{0} (原始图像) 不停迭代以上步骤就会得到整个金字塔。

以上过程描述了对图像的向下采样,如果将图像变大呢?:
首先,将图像在每个方向扩大为原来的两倍,新增的行和列以0填充(0)
使用先前同样的内核(乘以4)与放大后的图像卷积,获得 “新增像素” 的近似值。

这两个步骤(向下和向上采样) 分别通过OpenCV函数 pyrUp 和 pyrDown 实现, 我们将会在下面的示例中演示如何使用这两个函数。

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <math.h>
#include <stdlib.h>
#include <stdio.h>

using namespace cv;

/// 全局变量
Mat src, dst, tmp;
char* window_name = "Pyramids Demo";


/**
 * @函数 main
 */
int main( int argc, char** argv )
{
  /// 指示说明
  printf( "\n Zoom In-Out demo  \n " );
  printf( "------------------ \n" );
  printf( " * [u] -> Zoom in  \n" );
  printf( " * [d] -> Zoom out \n" );
  printf( " * [ESC] -> Close program \n \n" );

  /// 测试图像 - 尺寸必须能被 2^{n} 整除
  src = imread( "../images/chicky_512.jpg" );
  if( !src.data )
    { printf(" No data! -- Exiting the program \n");
      return -1; }

  tmp = src;
  dst = tmp;

  /// 创建显示窗口
  namedWindow( window_name, CV_WINDOW_AUTOSIZE );
  imshow( window_name, dst );

  /// 循环
  while( true )
  {
    int c;
    c = waitKey(10);

    if( (char)c == 27 )
      { break; }
    if( (char)c == 'u' )
      { pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 ) );
        printf( "** Zoom In: Image x 2 \n" );
      }
    else if( (char)c == 'd' )
     { pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 ) );
       printf( "** Zoom Out: Image / 2 \n" );
     }

    imshow( window_name, dst );
    tmp = dst;
  }
  return 0;
}

7. 图像分割

7.1. 图像阈值化

double threshold( InputArray src, OutputArray dst,
double thresh, double maxval, int type );
参数说明
src:原始数组,可以是Mat类型。
dst:输出数组,必须与 src 的类型一致。
threshold:阈值
maxval:使用 CV_THRESH_BINARY 和 CV_THRESH_BINARY_INV 的最大值。
type:阈值类型

type=CV_THRESH_BINARY:如果 src(x,y)>threshold ,dst(x,y) = max_value; 否则,dst(x,y)=0;
type=CV_THRESH_BINARY_INV:如果 src(x,y)>threshold,dst(x,y) = 0; 否则,dst(x,y) = max_value.
type=CV_THRESH_TRUNC:如果 src(x,y)>threshold,dst(x,y) = max_value; 否则dst(x,y) = src(x,y).
type=CV_THRESH_TOZERO:如果src(x,y)>threshold,dst(x,y) = src(x,y) ; 否则 dst(x,y) = 0。
type=CV_THRESH_TOZERO_INV:如果 src(x,y)>threshold,dst(x,y) = 0 ; 否则dst(x,y) = src(x,y)

7.2. 图像锐化

三种锐化算子

/*
 * @Descripttion: 
 * @version: 
 * @Author: Yueyang
 * @email: 1700695611@qq.com
 * @Date: 2021-05-02 23:45:24
 * @LastEditors: Yueyang
 * @LastEditTime: 2021-05-03 11:39:23
 */
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
using namespace std;
int main()
{
    Mat srcImage,dstImage1,dstImage2,dstImage;
    Mat kernel;
    srcImage=imread("F:/SDK/OpenCV/HiKivision/picture/lena.jpg");
    cvtColor(srcImage,srcImage,CV_BGR2GRAY);
//ROBERT X
    kernel =(Mat_<int>(2,2)<<1,0,0,-1);
    filter2D(srcImage,dstImage1,srcImage.depth(),kernel);
    kernel =(Mat_<int>(2,2)<<0,1,-1,0);
    filter2D(srcImage,dstImage2,srcImage.depth(),kernel);
//ROBERT Y      
    imwrite("F:/SDK/OpenCV/HiKivision/picture/robertx.jpg",dstImage1);
    imwrite("F:/SDK/OpenCV/HiKivision/picture/roberty.jpg",dstImage2);
    dstImage= dstImage1+dstImage2;
    imwrite("F:/SDK/OpenCV/HiKivision/picture/robert.jpg",dstImage);

//SOBEL X
    kernel =(Mat_<int>(3,3)<<-1,0,1,-2,0,2,-1,0,1);
    filter2D(srcImage,dstImage1,srcImage.depth(),kernel);
    kernel =(Mat_<int>(3,3)<<-1,-2,-1,0,0,0,1,2,1);
    filter2D(srcImage,dstImage2,srcImage.depth(),kernel);
//SOBEL Y      
    imwrite("F:/SDK/OpenCV/HiKivision/picture/sobelx.jpg",dstImage1);
    imwrite("F:/SDK/OpenCV/HiKivision/picture/sobely.jpg",dstImage2);
    dstImage= dstImage1+dstImage2;
    imwrite("F:/SDK/OpenCV/HiKivision/picture/sobel.jpg",dstImage);

//LAPLACE
    kernel =(Mat_<int>(3,3)<<0,-1,0,-1,4,-1,0,-1,0);
    filter2D(srcImage,dstImage,srcImage.depth(),kernel);
    imwrite("F:/SDK/OpenCV/HiKivision/picture/laplace.jpg",dstImage);

    return 0;
}


7.3. Canny 边缘检测

void Canny( InputArray image, OutputArray edges,
double threshold1, double threshold2,
int apertureSize = 3, bool L2gradient = false );

详细原理见

Canny原理

7.4. 轮廓发现

void findContours( InputOutputArray image, OutputArrayOfArrays contours,
OutputArray hierarchy, int mode,
int method, Point offset = Point());

/*
 * @Descripttion: 
 * @version: 
 * @Author: Yueyang
 * @email: 1700695611@qq.com
 * @Date: 2021-05-03 13:58:49
 * @LastEditors: Yueyang
 * @LastEditTime: 2021-05-03 14:06:53
 */
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main() {
    Mat g_srcImage;
    Mat g_grayImage;
    Mat g_cannyMat_output;
    vector<vector<Point> > g_vContours;
    vector<Vec4i> g_vHierarchy;
	g_srcImage = imread("F:/SDK/OpenCV/HiKivision/picture/apple.jpg");
	if (!g_srcImage.data) {
		printf("图片读入错误!\n");
		return false;
	}
	cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
	blur(g_grayImage, g_grayImage, Size(3, 3));
	Canny(g_grayImage, g_cannyMat_output, 10, 120, 3);
	//寻找轮廓
	findContours(g_cannyMat_output, g_vContours, g_vHierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
	//绘出轮廓
	Mat drawing = Mat::zeros(g_cannyMat_output.size(), CV_8UC3);
	for (int i = 0; i < g_vContours.size(); i++) {
		drawContours(drawing, g_vContours, i, Scalar(0,0,255), 2, 8, g_vHierarchy, 0, Point());
	}
    imwrite("F:/SDK/OpenCV/HiKivision/picture/contour.jpg",drawing);
	return 0;
}

8. 霍夫变换

HOUGH变换原理

HoughLinesP原函数:

功能:将输入图像按照给出参数要求提取线段,放在lines中。

lines:是一个vector,Vec4i是一个包含4个int数据类型的结构体,[x1,y1,x2,y2],可以表示一个线段。
rho:就是一个半径的分辨率。
theta:角度分辨率。
threshold:判断直线点数的阈值。
minLineLength:线段长度阈值。
minLineGap:线段上最近两点之间的阈值。

//内部集成Canny
HoughCircles(re_noise,cv2.HOUGH_GRADIENT,1,20,param1==100,param2=30,minRadius=0,maxRadius=100)

参数说明:
InputArray: 输入图像,数据类型一般用Mat型即可,需要是8位单通道灰度图像

OutputArray:存储检测到的圆的输出矢量

method:使用的检测方法,目前opencv只有霍夫梯度法一种方法可用,该参数填HOUGH_GRADIENT即可(opencv 4.1.0下)

dp:double类型的dp,用来检测圆心的累加器图像的分辨率于输入图像之比的倒数,且此参数允许创建一个比输入图像分辨率低的累加器。上述文字不好理解的话,来看例子吧。例如,如果dp= 1时,累加器和输入图像具有相同的分辨率。如果dp=2,累加器便有输入图像一半那么大的宽度和高度。

minDist:为霍夫变换检测到的圆的圆心之间的最小距离

param1:它是第三个参数method设置的检测方法的对应的参数。对当前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示传递给canny边缘检测算子的高阈值,而低阈值为高阈值的一半。

param2:也是第三个参数method设置的检测方法的对应的参数,对当前唯一的方法霍夫梯度法HOUGH_GRADIENT,它表示在检测阶段圆心的累加器阈值。它越小的话,就可以检测到更多根本不存在的圆,而它越大的话,能通过检测的圆就更加接近完美的圆形了。

minRadius:表示圆半径的最小值

maxRadius:表示圆半径的最大值

/*
 * @Descripttion: 
 * @version: 
 * @Author: Yueyang
 * @email: 1700695611@qq.com
 * @Date: 2021-05-03 12:26:02
 * @LastEditors: Yueyang
 * @LastEditTime: 2021-05-03 13:09:51
 */
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
    Mat srcImage,dstImage,gray;
    srcImage=imread("F:/SDK/OpenCV/HiKivision/picture/line.png");
    cvtColor(srcImage,srcImage,CV_BGR2GRAY);

    //边缘检测
    Canny(srcImage ,gray,100,130);
    imwrite("F:/SDK/OpenCV/HiKivision/picture/canny.jpg",dstImage);
    cvtColor(gray,dstImage,CV_GRAY2BGR);
    vector<Vec4f>lines;
    HoughLinesP(gray,lines,1,CV_PI/180,50);
    for(int i=0;i<lines.size();i++)
    {
        Vec4f hline=lines[i];
        line(dstImage,Point(hline[0],hline[1]),Point(hline[2],hline[3]),Scalar(0,0,255),3,LINE_AA);
    }
    imwrite("F:/SDK/OpenCV/HiKivision/picture/hough.jpg",dstImage);
    
    
    srcImage=imread("F:/SDK/OpenCV/HiKivision/picture/apple.jpg");
    cvtColor(srcImage,dstImage,CV_BGR2GRAY);
    vector<Vec3f>circles;
    HoughCircles(dstImage,circles,CV_HOUGH_GRADIENT,1,100,100,70,100,300);
    srcImage.copyTo(dstImage);
    for(int i=0;i<circles.size();i++)
    {
        Vec3f cc=circles[i];
        circle(dstImage,Point(cc[0],cc[1]),cc[2],Scalar(0,0,255),2,LINE_AA);
    }
    imwrite("F:/SDK/OpenCV/HiKivision/picture/houghc.jpg",dstImage);

    
    return 0;
}


9. 像素重映射

在opencv 中实现坐标变换的函数是remap,函数原型如下:

void cv::remap(InputArray src,
OutputArray dst,
InputArray map1,
InputArray map2,
int interpolation,
int borderMode = BORDER_CONSTANT,
const Scalar& borderValue = Scalar());

参数:

src : 原始图像;

dst : 目标图像,大小和map1的大小相同,数据类型和src的数据类型一样;

map1:表示(x,y)坐标点或者是x坐标,类型为CV_16SC2,CV_32FC1或者CV_32FC2;

map2: 表示y坐标,类型是CV_16UC1, CV_32FC1,当map1是(x,y)坐标时,map2可以为空;

Interpolation:表示插值算法,枚举类型主要。暂不支持INTER_AREA 插值算法。插值算法有一下几种:

/*
 * @Descripttion: 
 * @version: 
 * @Author: Yueyang
 * @email: 1700695611@qq.com
 * @Date: 2021-05-03 13:15:17
 * @LastEditors: Yueyang
 * @LastEditTime: 2021-05-03 13:17:09
 */
#include <iostream>
#include <opencv2/opencv.hpp>

int main(int argc,char* argv[])
{
    cv::Mat srcImage = cv::imread("F:/SDK/OpenCV/HiKivision/picture/lena.jpg");
    if (srcImage.empty())
    {
        std::cerr<< "fail to load image " <<std::endl;
        return -1;
    }
    cv::Mat xMapArray(srcImage.size(),CV_32FC1);
    cv::Mat yMapArray(srcImage.size(),CV_32FC1);
    cv::Mat dstImage(srcImage.size(),xMapArray.type());

    //设置变换x,y
    int rows = srcImage.rows;
    int cols = srcImage.cols;
    for(int i=0;i<rows;i++)
    {
        for(int j=0;j<cols;j++)
        {
            xMapArray.at<float>(i,j) = cols - j;
            yMapArray.at<float>(i,j) = rows - i;
        }
    }
    //进行变换
    cv::remap(srcImage,dstImage,xMapArray,yMapArray,cv::INTER_LINEAR,cv::BORDER_CONSTANT,cv::Scalar(0,0,0));
    cv::imwrite("F:/SDK/OpenCV/HiKivision/picture/remap.jpg",dstImage);
    return 0;
}

10. 模板匹配

模板匹配是在一幅图像中寻找一个特定目标的方法之一,这种方法的原理非常简单,遍历图像中的每一个可能的位置,比较各处与模板是否“相似”,当相似度足够高时,就认为找到了我们的目标。OpenCV提供了6种模板匹配算法:

平方差匹配法CV_TM_SQDIFF    
归一化平方差匹配法  CV_TM_SQDIFF_NORMED     
相关匹配法CV_TM_CCORR   
归一化相关匹配法CV_TM_CCORR_NORMED  
相关系数匹配法CV_TM_CCOEFF  
归一化相关系数匹配法    CV_TM_CCOEFF_NORMED 

用T表示模板图像,I表示待匹配图像,切模板图像的宽为w高为h,用R表示匹配结果,匹配过程如下图所示:

/*
 * @Descripttion: 
 * @version: 
 * @Author: Yueyang
 * @email: 1700695611@qq.com
 * @Date: 2021-05-03 13:35:39
 * @LastEditors: Yueyang
 * @LastEditTime: 2021-05-03 13:50:04
 */
/*
 * @Descripttion: 
 * @version: 
 * @Author: Yueyang
 * @email: 1700695611@qq.com
 * @Date: 2021-05-02 23:45:24
 * @LastEditors: Yueyang
 * @LastEditTime: 2021-05-02 23:58:45
 */
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
    Mat srcImage,matchImage,result;
    srcImage=imread("F:/SDK/OpenCV/HiKivision/picture/lena.jpg");
    matchImage=imread("F:/SDK/OpenCV/HiKivision/picture/eye.png");

	int result_cols = srcImage.cols - matchImage.cols + 1;
	int result_rows = srcImage.rows - matchImage.rows + 1;
	result.create(result_cols, result_rows, CV_32FC1);

    matchTemplate(srcImage,matchImage,result,CV_TM_SQDIFF_NORMED);
	normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());

	double minVal = -1;
	double maxVal;
	Point minLoc;
	Point maxLoc;
	Point matchLoc;
	cout << "匹配度:" << minVal << endl;
	minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, Mat());


	cout << "匹配度:" << minVal << endl;

	matchLoc = minLoc;

	rectangle(srcImage, matchLoc, Point(matchLoc.x + matchImage.cols, matchLoc.y + matchImage.rows), Scalar(0, 255, 0), 2, 8, 0);

    imwrite("F:/SDK/OpenCV/HiKivision/picture/match.jpg",srcImage);
    return 0;
}

  • 10
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

与光同程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值