首页 > 技术文章 > opencv基础

embedded-linux 2017-01-01 01:26 原文

基础

opencv:开源计算视觉库,包含二维、三维。

PCL:Point Cloud Library点云库,偏向三维。

https://opencv.org/

学习之路:opencv2-看云 -csdn

opencv 4.0中文文档

opencv 4.2.0 api

OpenCV学习笔记-知乎

OpenCV4快速入门-git

OpenCV4快速入门 示例-csdn

ubuntu20.0下使用opencv 4.2.0版本,安装(How to Install OpenCV on Ubuntu 20.04)

sudo apt install libopencv-dev python3-opencv

$ pkg-config --libs --cflags opencv4
-I/usr/include/opencv4/opencv -I/usr/include/opencv4 \
-lopencv_stitching -lopencv_aruco \
-lopencv_bgsegm -lopencv_bioinspired \
-lopencv_ccalib -lopencv_dnn_objdetect \
-lopencv_dnn_superres -lopencv_dpm \
-lopencv_highgui -lopencv_face \
-lopencv_freetype -lopencv_fuzzy \
-lopencv_hdf -lopencv_hfs \
-lopencv_img_hash \
-lopencv_line_descriptor \
-lopencv_quality -lopencv_reg \
-lopencv_rgbd -lopencv_saliency \
-lopencv_shape -lopencv_stereo \
-lopencv_structured_light \
-lopencv_phase_unwrapping \
-lopencv_superres -lopencv_optflow \
-lopencv_surface_matching \
-lopencv_tracking -lopencv_datasets \
-lopencv_text -lopencv_dnn \
-lopencv_plot -lopencv_ml \
-lopencv_videostab -lopencv_videoio \
-lopencv_viz -lopencv_ximgproc \
-lopencv_video -lopencv_xobjdetect \
-lopencv_objdetect -lopencv_calib3d \
-lopencv_imgcodecs -lopencv_features2d \
-lopencv_flann -lopencv_xphoto \
-lopencv_photo -lopencv_imgproc \
-lopencv_core 

// 头文件:/usr/include/opencv4/
// 库文件:/usr/lib/x86_64-linux-gnu/

Opencv-python是一个Python绑定库,旨在解决计算机视觉问题。OpenCV-Python 使用了 Numpy,这是一个有着 MATLAB 风格语法,高度优化的用于数值计算的库。所有 OpenCV 数组结构都与 Numpy 数组进行转换。这也使得与使用 Numpy 的其他库(如 SciPy 和 Matplotlib)集成更容易。数值编程工具:NumPy的详细教程-csdn

vscode中配置opecv-python路径:
VSCode -> 文件 ->首选项 -> 设置 -> 用户 -> 扩展 -> Python ->Auto Complete: Extra Paths -> 在settings.json中编辑:

"python.autoComplete.extraPaths": []

在opencv中,图像处理用数据结构图cv::Mat(按像素点存储处理,一般为BGR),RGB图像的通道顺序为BGR。

模块

OpenCV库自2.2版本起就被划分成多个模块,在进行开发之前,需要将这些模块编译成库文件,然后在lib文件夹中找到这些模块:

  • opencv_core模块:其中包含OpenCV基本数据结构、动态数据结构、绘图与数组操作的相关函数、辅助功能与系统函数、基本的算法函数等核心功能。
  • opencv_improc模块:包含图像处理函数,主要包含图像滤波、图像的几何变换、直方图、特征检测、目标跟踪等内容。
  • opencv_highgui模块:高层GUI图形用户界面,包含媒体的I/O输入输出函数,读写图像及视频的函数,以及操作图形用户界面函数。
  • opencv_features2d模块:即2D功能框架,包含兴趣点检测子,描述子以及兴趣点匹配框架。
  • opencv_calib3d模块:Calibration(校准)加3D这两个词的组合缩写。这个模块主要是相机校准和三维重建相关的内容,包含相机标定,双目几何估计,物体姿态估计以及立体视觉等函数。
  • opencv_video模块:包含运动估算,特征跟踪以及前景提取函数与相关的类。
  • opencv_objdetect模块:主要由级联分类(Cascade Classification)和Latent SVM这两个部分。其中包括物体检测函数,如脸部和行人检测。
  • opencv_stitching模块:OpenCV2.4.0新增的模块,其主要功能是实现图像拼接。
  • lopencv_superres模块:即SuperResolution,利用多种算法实现超分辨率技术的相关功能模块。
  • opencv_ml模块:机器学习模块,主要包括统计模型 (Statistical Models)、一般贝叶斯分类器 (Normal Bayes Classifier)、K-近邻 (K-NearestNeighbors)、支持向量机 (Support Vector Machines)、决策树 (Decision Trees)、提升(Boosting)、梯度提高树(Gradient Boosted Trees)、随机树 (Random Trees)、超随机树 (Extremely randomized trees)、期望最大化 (Expectation Maximization)、神经网络 (Neural Networks)等内容。
  • opencv_flann模块:高维的近似近邻快速搜索算法库, 主要由两个部分组成:快速近似最近邻搜索和聚类。
  • opencv_contrib模块:第三方代码,包括一些新添加的不太稳定的可选功能,如新型的人脸识别、立体匹配、人工视网膜模型等技术。
  • opencv_nonfree模块:包含一些拥有专利的算法,如SIFT、SURF函数源码。

头文件

include/opencv2/opencv.hpp包含OpenCV各个模块的头文件。

可以仅仅包含opencv.hpp一个头文件(其包含了所有可能在OpenCV函数中用到的头文件),但是会减慢编译的速度。

数据结构

数据类型

CV_{元素比特数}{元素类型}C{通道数}

一个有效的数据类型需要同时指明数据的类型和通道数。包括CV_{8S,8U,16S,16U,32F,64F}C{1,2,3}的多种组合,比如CV_32FC3表示一个三通道的32位浮点数据。最常见的 CV_8UC3 就表示为 3通道Unsigned 8bits 格式的矩阵。

cv::Scalar

cv::Scalar是四维点类,是四维双精度向量的快速表示,包含一个长度为4的double数组,最多可以存储4个值,没有提供的值默认是0。

cv::Scalar本质是一个四维Point类,与一个可以产生任意四元素向量的模板相关,但cv::Scalar一般是双精度四元素向量的别名。cv::Scalar对象的元素通过整数下标来访问。

常用场景:

Mat m(7, 7, CV_32FC2, Scalar(1,3));

创建一个2通道,且每个通道的值为(1,3),深度为32(32位),7行7列的图像矩阵。

cv::Mat

cv::Mat类用来代表任意维度的包含任意基础元素的数组,存储图片对象是cv::Mat类的特殊用途。cv::Mat类用于表示任意维度的稠密数组。“稠密”表示该数组的所有部分都有一个值存储,即使这个值是0。对于大多数图像来说,都是以稠密数组的形式存储的;与之相对的是稀疏数组,稀疏数组中只有非0的数值会被存储。从结论上说,如果数组的很多地方都是0,那么稀疏数组会非常节约内存。

cv::SparseMat类表征稀疏矩阵,更适用于直方图这样的稀疏数据。

class CV_EXPORTS Mat
{
public:
    //一系列函数
    /* The method returns the matrix element size in bytes. For example, if the matrix type is CV_16SC3 ,
    the method returns 3\*sizeof(short) or 6.
     */
    size_t elemSize() const;

    /** @brief Returns the size of each matrix element channel in bytes.

    The method returns the matrix element channel size in bytes, that is, it ignores the number of
    channels. For example, if the matrix type is CV_16SC3 , the method returns sizeof(short) or 2.
     */
    size_t elemSize1() const;

    /** @brief Returns the type of a matrix element.

    The method returns a matrix element type. This is an identifier compatible with the CvMat type
    system, like CV_16SC3 or 16-bit signed 3-channel array, and so on.
     */
    int type() const;

    /** @brief Returns the depth of a matrix element.

    The method returns the identifier of the matrix element depth (the type of each individual channel).
    For example, for a 16-bit signed element array, the method returns CV_16S . A complete list of
    matrix types contains the following values:
    -   CV_8U - 8-bit unsigned integers ( 0..255 )
    -   CV_8S - 8-bit signed integers ( -128..127 )
    -   CV_16U - 16-bit unsigned integers ( 0..65535 )
    -   CV_16S - 16-bit signed integers ( -32768..32767 )
    -   CV_32S - 32-bit signed integers ( -2147483648..2147483647 )
    -   CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )
    -   CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )
     */
    int depth() const;

    /** @brief Returns the number of matrix channels.

    The method returns the number of matrix channels.
     */
    int channels() const;

    /** @brief Returns a normalized step.

    The method returns a matrix step divided by Mat::elemSize1() . It can be useful to quickly access an
    arbitrary matrix element.
     */
    size_t step1(int i=0) const;
    ...
    /* flag 参数中包含许多关于矩阵的信息,如:
    -Mat 的标识
    -数据是否连续
    -深度
    -通道数目
    */
    int flags;
    //矩阵的维数,取值应该大于或等于 2
    int dims;
    //矩阵的行数和列数,如果矩阵超过 2 维,这两个变量的值都为-1
    int rows, cols;
    //指向数据的指针
    uchar* data;
    //指向引用计数的指针
    //如果数据是由用户分配的,则为 NULL
    int* refcount;
    
    MatSize size;
    MatStep step;

...
}

创建Mat对象

//使用构造函数
cv::Mat
cv::Mat(int rows, int cols, int type);
cv::Mat(int rows, int cols, int type, const Scalar &s); // 指定初始化值
cv::Mat(int rows, int cols, int type, void *data, size_t step=AUTO_STEP);
// 指定预先存储的数据
cv::Mat(cv::Size sz, int type);
cv::Mat(cv::Size sz, int type, const Scalar &s);
cv::Mat(cv::Size sz, int type, void *data, size_t step=AUTO_STEP);
cv::Mat(int ndims, const int *sizes, int type); // 指定类型的多维数组
cv::Mat(int ndims, const int* sizes, int type, const Scalar&s);
cv::Mat(int ndims, const int* sizes, int type, void *data, size_t step=AUTO_STEP);

cv::Mat(const Mat &mat);
cv::Mat(const Mat &mat, const cv::Range&rows, const cv::Range& cols); //只从指定的行列中复制数据
cv::Mat(const Mat &mat, const cv::Rect& roi); // 只从感兴趣的区域复制数据
cv::Mat(const Mat&mat, const cv::Range* ranges); //服务于n维数组,从泛化的感兴趣区域复制数据;
cv::Mat(const cv::MatExpr &expr); // 从其他矩阵的线性代数中生成新矩阵

// cv::Mat创建的数组,没有大小和类型,通过create申请内存区域
cv::Mat m;
m.create(3, 10, CV_32FC3);
m.setTo(cv::Scalar(1.0f, 0.0f, 1.0f);
cv::Mat m(3, 10, CV_32FC3, cv::Scalar(1.0f, 0.0f, 1.0f));

// 构造cv::Mat的静态方法
cv::Mat::zeros(rows, cols, type); //值全为0
cv::Mat::ones(rows, cols, type); // 值全为1
cv::Mat::eye(rows, cols, type); // 单位矩阵
Mat M(3,2,CV_8SC3,Scalar(0,0,4));
cout<<"M="<<M<<endl;
Mat M(600,800,CV_8UC1);
cout<<"M="<<M<<endl;

//使用create()函数创造,只申请,不初始化
Mat M(2,2, CV_8UC3);//构造函数创建图像
M.create(3,2, CV_8UC2);//释放内存重新创建图像

//Matlab风格创建
Mat Z = Mat::zeros(2,3, CV_8UC1);
cout << "Z = " << endl << " " << Z << endl;
Mat O = Mat::ones(2, 3, CV_32F);
cout << "O = " << endl << " " << O << endl;
Mat E = Mat::eye(2, 3, CV_64F);
cout << "E = " << endl << " " << E << endl;

获取数组元素

访问一个元素的两种主要方法是通过位置或迭代器访问。

  • at()函数

直接访问是通过模板函数at<>()实现的。这个函数有很多变体,对不同维度的数组有不同的参数要求。这个函数的工作方式是先将at<>()特化到矩阵所包含的数据类型,然后使用所想要的数据的行和列的位置访问该元素。

cv::Mat m = cv::Mat::eye(10, 10, 32FC2);
printf("Element (3, 3) is (%f, %f)\n",
    m.at<cv::Vec2f>(3,3)[0], m.at<cv::Vec2f>(3,3)[1]);

需要注意的是,如果要遍历图像,并不推荐使用at()函数。使用这个函数的优点是代码的可读性高,但是效率并不是很高。

  • 使用迭代器

cv::Mat内嵌迭代器机制,提供一对迭代器模板,一个用于只读(const)数组的和一个用于非只读的(non-const)数组的。上述迭代器分别被命名为cv::MatConstIterator<>和cv::MatIterator<>。cv::Mat的成员函数begin()和end()会返回这种类型的对象。因为迭代器由足够的能力来处理连续的内存区域和非连续的内存区域,这种用法非常方便,不管在哪一种维度的数组中都非常有效。

int sz[3] = {4, 4, 4};
cv::Mat m(3, sz, CV_32FC3); // A three-dimensional array of size 4-by-4-by-4
cv::randu(m, -1.0f, 1.0f); // fill with random numbers from -1.0 to 1.0

float max = 0.0f;
cv::MatConstIterator<cv::Vec3f> it = m.begin();
while( it != m.end()){
    len2 = (*it)[0]*(*it)[0] + (*it)[1]*(*it)[1] + (*it)[2]*(*it)[2];
    if(len2 > max) max = len2;
    it++;
}
  • 使用数据指针

C++指针操作是不进行类型以及越界检查的,如果指针访问出错,程序有时候可能看上去一切正常,有时候却突然弹出“段错误”。若要求运行速度,那么遍历像素时,建议使用指针。

#include <iostream>
#include <opencv2/core.hpp>

using namespace std;
using namespace cv;
int main(int argc,char* argv[]){
  Mat grayim(600,600,CV_8UC1);
  Mat colorim(600,600,CV_8UC3);
  
  //遍历grayim
  for(int i=0;i<grayim.rows;++i){
    uchar* p=grayim.ptr<uchar>(i);
    for(int j=0;j<grayim.cols;++j){
      p[j]=(i+j)%255;
    }
  }
  //遍历colorimg
  for(int i=0;i<colorim.rows;++i){
    Vec3b * p=colorim.ptr<Vec3b>(i);
    for(int j=0;j<colorim.cols;++i){
      p[j][0]=i%255;
      p[j][1]=j%255;
      p[j][2]=j%255;
    }
  }
  
  //显示图片
  imshow("colorim",colorim);
  imshow("grayim",grayim);
  return 0;
}

其他

cv::Vec<>固定向量类,不同于STL类,固定向量类用于在编译时已经知道维度的小型向量。这允许代码以更高的效率处理与此类向量相关的操作。常使用typedef别名,cv::Vec2i,cv::Vec3i和cv::Vec4d。

cv::Matx<>固定矩阵类,与固定向量类一样,用于一些特定的小型矩阵操作。固定矩阵类的维度在编译之前必须已知。固定矩阵类实际是一个模板,cv::Matx<>,独立的矩阵通常通过别名分配,cv::Matx{1,2,...}{1,2,...}{f,d},其中数字是1到6之间的任何数。
一般情况下,当表示一个处理矩阵代数的矩阵时,会用固定矩阵类。而如果对象是一个图像或大型点列表这样的大数组时,应使用cv::Mat。

cv::Point类是两到三个原语类型的容器,Point类与固定向量类之间最大的不同是Point类成员通过名称变量访问(mypoint.x,mypoint.y等)而不是通过下标访问(myvec[0],myvec[1]等)。常用别名访问,cv::Point2i,cv::Point2f,cv::Point2d或cv::Point3i, cv::Point3f, cv::Point3d。

cv::Size类类似Point类,可以户型转换。不同为size类对应的成员是width和height。size类三个别名分别是cv::Size, cv::Size2i,cv::Size2f。

cv::Rect类包含Point类成员x和y(矩形左上角)和size类成员width和height。然而矩形类并不是从Point类或Size类继承过来的,所以并灭有从它们那里继承操作。

cv::RotateRect类包含一个中心点cv::Point2f、一个大小cv::Size2f和一个额外的角度float的容器。其中浮点类型float的角度代表图形绕中心点旋转的角度。cv::RotatedRect以中心为原点。

复数类与STL复数类模板complex<>不一样,但与之兼容,可以相互转换,最大区别在于成员获取。STL中通过成员函数real()和imag()获取的,而Opencv中通过成员变量re和im获取。

cv::Range类用于确定一个连续的整数序列。cv::Range对象由两个元素start和end(初始值start,不包含终止值end),cv::Range rng(0, 4)包含值0,1,2,3,不包含4。

开发配置

vscode配置:

// c_cpp_properties.json 配置opencv头文件路径
{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/usr/include/opencv4"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "gnu17",
            "cppStandard": "gnu++14",
            "intelliSenseMode": "linux-gcc-x64"
        }
    ],
    "version": 4
}

// tasks.json 配置编译参数
{
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: g++ build active file",
            "command": "/usr/bin/g++",
            "args": [
                "-g",
                "*.cpp",
                "-lopencv_core",
                "-lopencv_highgui",
                "-lopencv_imgcodecs",
                "-lopencv_imgproc",
                "-lopencv_shape",
                "-lopencv_videoio",
                "-I/usr/include/opencv4/opencv2/",
                "-I/usr/include/opencv4/",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Task generated by Debugger."
        }
    ],
    "version": "2.0.0"
}

ros环境配置

参考:ROS系统学习5---OpenCV的使用

创建rosOpenCV包

catkin_create_pkg ros_OpenCV sensor_msgs cv_bridge roscpp std_msgs image_transport

// CMakeLists.txt配置
find_package(OpenCV)
include_directories(${catkin_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS})
add_executable(ros_OpenCV ./src/ros_OpenCV.cpp)
target_link_libraries(ros_OpenCV ${OpenCV_LIBS} ${catkin_LIBRARIES})

注:需要在c_cpp_properties.json中配置opencv的头文件路径:"/usr/include/opencv4",以便找到头文件。

应用

显示图像

#include <opencv2/opencv.hpp>

using namespace cv;

int main(int argc, char* argv[]) {
    if (argc != 2) {
        printf("usage: image.out <img_path>\n");
        return -1;
    }
    
    Mat img_mat;
    img_mat = imread(argv[1], 1);
    if (!img_mat.data) {
        printf("NO image data\n");
        return -1;
    }
    imshow("img", img_mat);
    waitKey(2000);
    return 0;
}
////
g++ -lopencv_core -lopencv_highgui -lopencv_imgcodecs -lopencv_imgproc -I/usr/include/opencv4/opencv2/ -I/usr/include/opencv4/ -Wall -o a.out

g++ show.cpp $(pkg-config --libs --cflags opencv4) 

摄像头校准

camera_calibration - ROS

推荐阅读