zoukankan      html  css  js  c++  java
  • OpenCV入门(1)- 简介

    1、图像的表示

      在计算机看来,图像只是一些亮度各异的点,一副M*N的图片可以用M*N的矩阵来表示,矩阵的值表示这个位置上像素的亮度。

      一般灰度图用二维矩阵来表示,彩色(多通道)图用三维矩阵表示,大部分设备都是用无符号8位整数(CV_8U)表示像素的亮度。

     

    2、Mat类

    OpenCV中使用Mat类(Matrix的简称)来表示图片,能够自动管理内存。

    class CV_EXPORTS Mat
    {
    public:
     //一系列函数
     ...
     /* flag 参数中包含许多关于矩阵的信息,如:
     -Mat 的标识
     -数据是否连续
     -深度
     -通道数目
     */
     int flags;
     //矩阵的维数,取值应该大于或等于 2
     int dims;
     //矩阵的行数和列数,如果矩阵超过 2 维,这两个变量的值都为-1
     int rows, cols;
     //指向数据的指针
     uchar* data;
     //指向引用计数的指针
     //如果数据是由用户分配的,则为 NULL
     int* refcount;
    24
     //其他成员变量和成员函数
     ...
    };

    使用构造函数来创建Mat对象:

    Mat M(3,2, CV_8UC3, Scalar(0,0,255));
    // 创建一个行数为3,列数为2的图像,像素元素是8位无符号整型,初始化为0,0,255(默认颜色顺序为BGR) cout
    << "M = " << endl << " " << M << endl;

    重新创建对象: 

    Mat M(2,2, CV_8UC3);//构造函数创建图像
    M.create(3,2, CV_8UC2);//释放内存重新创建图像,但无法设置像素的初始值

    矩阵基本元素表达的话,单通道一般用8U,当然也可以是16S、32F等,这些数据类型可以直接用uchar/short/float等基本数据类型表达。但遇到多通道图形,如RGB彩色图像的时候,依然将图形视为一个二维矩阵,但矩阵的元素不再是基本数据类型,而是使用OpenVC中的模板类Vec表示:

    typedef Vec<uchar, 2> Vec2b;
    typedef Vec<uchar, 3> Vec3b;
    typedef Vec<uchar, 4> Vec4b;
    typedef Vec<short, 2> Vec2s;
    typedef Vec<short, 3> Vec3s;
    typedef Vec<short, 4> Vec4s;
    typedef Vec
    <int, 2> Vec2i; typedef Vec<int, 3> Vec3i; typedef Vec<int, 4> Vec4i;
    typedef Vec
    <float, 2> Vec2f; typedef Vec<float, 3> Vec3f; typedef Vec<float, 4> Vec4f; typedef Vec<float, 6> Vec6f;
    typedef Vec
    <double, 2> Vec2d; typedef Vec<double, 3> Vec3d; typedef Vec<double, 4> Vec4d; typedef Vec<double, 6> Vec6d; Vec3b color; //用 color 变量描述一种 RGB 颜色 color[0]=255; //B 分量 color[1]=0; //G 分量 color[2]=0; //R 分量

    使用at()函数操作矩阵:

     Mat grayim(600, 800, CV_8UC1);
     Mat colorim(600, 800, CV_8UC3);
    //遍历所有像素,并设置像素值 for( int i = 0; i < grayim.rows; ++i)   for( int j = 0; j < grayim.cols; ++j )     grayim.at<uchar>(i,j) = (i+j)%255;
    //遍历所有像素,并设置像素值 for( int i = 0; i < colorim.rows; ++i) {
      
    for( int j = 0; j < colorim.cols; ++j ){     Vec3b pixel;     pixel[0] = i%255; //Blue     pixel[1] = j%255; //Green     pixel[2] = 0; //Red     colorim.at<Vec3b>(i,j) = pixel;   }
    }
    //显示结果 imshow("grayim", grayim); imshow("colorim", colorim);

    使用迭代器操作矩阵:

     Mat grayim(600, 800, CV_8UC1);
     Mat colorim(600, 800, CV_8UC3);
     //遍历所有像素,并设置像素值
     MatIterator_<uchar> grayit, grayend;
     for( grayit = grayim.begin<uchar>(), grayend = 
    grayim.end<uchar>(); grayit != grayend; ++grayit)
     *grayit = rand()%255;
     //遍历所有像素,并设置像素值
     MatIterator_<Vec3b> colorit, colorend;
     for( colorit = colorim.begin<Vec3b>(), colorend = 
    colorim.end<Vec3b>(); colorit != colorend; ++colorit)
     {
     (*colorit)[0] = rand()%255; //Blue
     (*colorit)[1] = rand()%255; //Green
     (*colorit)[2] = rand()%255; //Red
     }
     //显示结果
     imshow("grayim", grayim);
     imshow("colorim", colorim);
     waitKey(0);

    使用数据指针操作数据:

     // 高效,但是不进行类型以及越界检查
    Mat grayim(600, 800, CV_8UC1); Mat colorim(600, 800, CV_8UC3); //遍历所有像素,并设置像素值 for( int i = 0; i < grayim.rows; ++i) { //获取第 i 行首像素指针 uchar * p = grayim.ptr<uchar>(i); //对第 i 行的每个像素(byte)操作 for( int j = 0; j < grayim.cols; ++j ) p[j] = (i+j)%255; } //遍历所有像素,并设置像素值 for( int i = 0; i < colorim.rows; ++i) { //获取第 i 行首像素指针 Vec3b * p = colorim.ptr<Vec3b>(i); for( int j = 0; j < colorim.cols; ++j ) { p[j][0] = i%255; //Blue p[j][1] = j%255; //Green p[j][2] = 0; //Red } 32 } //显示结果 imshow("grayim", grayim); imshow("colorim", colorim); waitKey(0);

    选取图像局部区域

    Mat类提供了多钟方便的方法来选择图像局部区域,但这些方法并不进行内存复制操作。如果将图像局部区域赋值给新的Mat对象,新对象和原始对象将共用相同的数据区域。因为不申请内存,所以执行速度都比较快。

    1、单行或单列选取

    Mat Mat::row(int i) const
    Mat Mat::col(int j) const

    Mat line = A.row(i);

    2、多行或多列选择

    Range是OpenVC中新增的类,该类有两个关键变量start和end,可以用来表示矩阵的多个连续的行或者列。其表示的范围为从start到end,包含start但不包含end

    class Range
    {
    public:
     ...
     int start, end;
    };

    //创建一个单位阵
    Mat A = Mat::eye(10, 10, CV_32S);
    //提取第 1 到 3 列(不包括 3)
    Mat B = A(Range::all(), Range(1, 3));
    //提取 B 的第 5 至 9 行(不包括 9)
    //其实等价于 C = A(Range(5, 9), Range(1, 3))
    Mat C = B(Range(5, 9), Range::all());

    3、感兴趣的区域(Region of interest)

    从图像中可以提取感兴趣的区域,有两种方法

    //创建宽度为 320,高度为 240 的 3 通道图像
    Mat img(Size(320,240),CV_8UC3);
    //roi 是表示 img 中 Rect(10,10,100,100)区域的对象
    Mat roi(img, Rect(10,10,100,100));
    除了使用构造函数,还可以使用括号运算符,如下:
    Mat roi2 = img(Rect(10,10,100,100));
    当然也可以使用 Range 对象来定义感兴趣区域,如下:
    //使用括号运算符
    Mat roi3 = img(Range(10,100),Range(10,100));

     //使用构造函数
      Mat roi4(img, Range(10,100),Range(10,100));

    4、取对角线元素

    Mat Mat::diag(int d) const

    参数 d=0 时,表示取主对角线;当参数 d>0 是,表示取主对角线下方的次对
    角线,如 d=1 时,表示取主对角线下方,且紧贴主多角线的元素;当参数 d<0 时,
    表示取主对角线上方的次对角线。

    5、Mat表达式

    利用C++运算符重载,代码变的更简洁易懂

    下面给出 Mat 表达式所支持的运算。下面的列表中使用 A 和 B 表示 Mat 类
    型的对象,使用 s 表示 Scalar 对象,alpha 表示 double 值。
     加法,减法,取负:A+B,A-B,A+s,A-s,s+A,s-A,-A
     缩放取值范围:A*alpha
     矩阵对应元素的乘法和除法: A.mul(B),A/B,alpha/A
     矩阵乘法:A*B (注意此处是矩阵乘法,而不是矩阵对应元素相乘)
     矩阵转置:A.t()
     矩阵求逆和求伪逆:A.inv()
     矩阵比较运算:A cmpop B,A cmpop alpha,alpha cmpop A。此处 cmpop
    可以是>,>=,==,!=,<=,<。如果条件成立,则结果矩阵(8U 类型矩
    阵)的对应元素被置为 255;否则置 0。
     矩阵位逻辑运算:A logicop B,A logicop s,s logicop A,~A,此处 logicop
    可以是&,|和^35
     矩阵对应元素的最大值和最小值:min(A, B),min(A, alpha),max(A, B),
    max(A, alpha)。
     矩阵中元素的绝对值:abs(A)
     叉积和点积:A.cross(B),A.dot(B)

    6、读图像文件(需要安装对应图像格式的文件格式库)

    Mat imread(const string& filename, int flags=1 )
    
    1、flag>0,该函数返回 3 通道图像
    2、flag=0,该函数返回单通道图像
    3、flag<0,则函数不对图像进行通道转换。

    7、写图像文件

    bool imwrite(const string& filename, InputArray image, 
              const vector<int>& params=vector<int>())
    // params可以指定文件格式的一些信息

    8、读写图片案例

    //读入图像,并将之转为单通道图像
     Mat im = imread("lena.jpg", 0);
     //请一定检查是否成功读图
     if( im.empty() )
     {
     cout << "Can not load image." << endl;
     return -1;
     }
     //进行 Canny 操作,并将结果存于 result
     Mat result;
     Canny(im, result, 50, 150);
     //保存结果
     imwrite("lena-canny.png", result);

    9、读写视频

      视频的格式主要是由压缩算法决定,称之为编码器(coder),解压算法称之为解码器(decoder),统称codec。OpenVC使用VideoCapture和VideoWriter来实现视频的读写。

    //打开第一个摄像头
    //VideoCapture cap(0);
    //打开视频文件
    VideoCapture cap("video.short.raw.avi");

    //检查是否成功打开
    if(!cap.isOpened())
    {
    cerr << "Can not open a camera or file." << endl;
    return -1;
    }
    Mat edges;
    //创建窗口
    namedWindow("edges",1);
    for(;;)
    {
    Mat frame;
    //从 cap 中读一帧,存到 frame
    cap >> frame;
    //如果未读到图像
    if(frame.empty())
    break;
    //将读到的图像转为灰度图
    cvtColor(frame, edges, CV_BGR2GRAY);
    //进行边缘提取操作
    Canny(edges, edges, 0, 30, 3);
    //显示结果
    imshow("edges", edges);
    //等待 30 秒,如果按键则推出循环
    if(waitKey(30) >= 0)
    break;
    }
    //退出时会自动释放 cap 中占用资源
    return 0;

    //定义视频的宽度和高度
     Size s(320, 240);
     //创建 writer,并指定 FOURCC 及 FPS 等参数
     VideoWriter writer = VideoWriter("myvideo.avi", 
    CV_FOURCC('M','J','P','G'), 25, s);
     //检查是否成功创建
     if(!writer.isOpened())
     {
     cerr << "Can not create video file.
    " << endl;
     return -1;
     }
     //视频帧
     Mat frame(s, CV_8UC3);
     for(int i = 0; i < 100; i++)
     {
     //将图像置为黑色
     frame = Scalar::all(0);
     //将整数 i 转为 i 字符串类型
     char text[128];
     snprintf(text, sizeof(text), "%d", i);
     //将数字绘到画面上
     putText(frame, text, Point(s.width/3, s.height/3), 
    FONT_HERSHEY_SCRIPT_SIMPLEX, 3,
     Scalar(0,0,255), 3, 8);
     //将图像写入视频
     writer << frame;
     }
     //退出程序时会自动关闭视频文件
     return 0;
  • 相关阅读:
    HashTable源码浅析(基于jdk1.8.0_231)
    LinkedHashMap源码浅析(基于jdk1.8.0_231)
    SortedSet接口源码浅析(基于jdk1.8.0_231)
    NavigableSet接口源码浅析(基于jdk1.8.0_231)
    TreeSet源码浅析(基于jdk1.8.0_231)
    TreeMap源码浅析(基于jdk1.8.0_231)
    Map接口源码解析(基于jdk1.8.0_231)
    Arrays工具类源码详解(基于jdk1.8.0_231)
    Collections源码详解(基于jdk1.8.0_231)
    BitSet源码详解 (基于jdk1.8.0.231)
  • 原文地址:https://www.cnblogs.com/ijavanese/p/10340714.html
Copyright © 2011-2022 走看看