opencv小白入门教程

Xsens动作捕捉 2023-04-16 4738

OpenCV参考视频教程:实践才是学OpenCV的最好方法,练完这套完整版OpenCV实战案例比看100套教程强!(OpenCV安装/图像处理/计算机视觉/机器视觉)_哔哩哔哩_bilibili

opencv安装
1、编译源码
总结一下:主要是下载源码,然后可以解压源码,在主目录下:
mkdir build cd build cmake-gui ..
跳出cmake编译界面,注意源码路径和编译路径,可以选择配置,之后编译和安装:
make -j4 sudo make install
2、python接口
编译安装好了,在/usr/local/lib/python2.7/site-packages底下有一个cv2.so文件,可以把这个目录添加到环境变量PYTHONPATH中:
export PYTHONPATH=$PYTHONPATH:/usr/local/lib/python2.7/site-packages
这个在python中可以:
import cv2
如果是第三方python,则还需要将 /usr/local/lib/python2.7/site-packages 目录下的 cv2.so 复制到 第三方的python/usr/local/lib/python2.7/site-packages 目录下,这里以 anaconda为例:
sudo cp /usr/local/lib/python2.7/site-packages/cv2.so ~/anaconda/lib/python2.7/site-packages
使用第三方python导入编译好的包的过程中,有时候会出现以下类型的错误:
libstdc++.so.6:GLIBCXX_3.4.21 not found
这一般的原因是python 支持的编译器版本比依赖库的编译器版本低,在python中低版本调用了高版本的gcc库文件,从而导致了这类错误。错误1错误2
opencv文件结构
编译好的opencv会产生/bin/include/lib三个文件夹分别拷贝到cmake中选择的安装目录下(/usr/local/),我们可以从暴露的头文件看一下opencv的目录结构:
/usr/local/include中,我们使用tree -d查看目录结构,可以看到
├── opencv └── opencv2 ├── calib3d ├── core │ └── cuda │ └── detail ├── features2d ├── flann ├── hal ├── highgui ├── imgcodecs ├── imgproc ├── ml ├── objdetect ├── photo ├── shape ├── stitching │ └── detail ├── superres ├── video ├── videoio └── videostab
opencv文件是opencv1.0的核心,文件底下是一些头文件的集合,
opencv ├── cvaux.h ├── cvaux.hpp ├── cv.h ├── cv.hpp ├── cvwimage.h ├── cxcore.h ├── cxcore.hpp ├── cxeigen.hpp ├── cxmisc.h ├── highgui.h └── ml.h
opencv2是新版opencv2系列的头文件的集合,底下有不同的组件的包。
opencv2 ├── calib3d ├── core │ └── cuda │ └── detail ├── features2d ├── flann ├── hal ├── highgui ├── imgcodecs ├── imgproc ├── ml ├── objdetect ├── photo ├── shape ├── stitching │ └── detail ├── superres ├── video ├── videoio └── videostab
有时候我们经常在c++中写:
#include <opencv2/core.hpp> #include <opencv2/highgui.hpp>
有时候也会写:
#include <opencv/cv.h>
主要是引用了不同版本的头文件。opencv的模块可以从include里面看出:一些简单的介绍如下:
【calib3d】——其实就是就是Calibration(校准)加3D这两个词的组合缩写。这个模块主要是相机校准和三维重建相关的内容。基本的多视角几何算法,单个立体摄像头标定,物体姿态估计,立体相似性算法,3D信息的重建等等。
【contrib】——也就是Contributed/Experimental Stuf的缩写, 该模块包含了一些最近添加的不太稳定的可选功能,不用去多管。2.4.8里的这个模块有新型人脸识别,立体匹配,人工视网膜模型等技术。
【core】——核心功能模块,包含如下内容:

  • OpenCV基本数据结构
  • 动态数据结构
  • 绘图函数
  • 数组操作相关函数
  • 辅助功能与系统函数和宏
  • 与OpenGL的互操作

【imgproc】——Image和Processing这两个单词的缩写组合。图像处理模块,这个模块包含了如下内容:

  • 线性和非线性的图像滤波
  • 图像的几何变换
  • 其它(Miscellaneous)图像转换
  • 直方图相关
  • 结构分析和形状描述
  • 运动分析和对象跟踪
  • 特征检测
  • 目标检测等内容

【features2d】 ——也就是Features2D, 2D功能框架 ,包含如下内容:

  • 特征检测和描述
  • 特征检测器(Feature Detectors)通用接口
  • 描述符提取器(Descriptor Extractors)通用接口
  • 描述符匹配器(Descriptor Matchers)通用接口
  • 通用描述符(Generic Descriptor)匹配器通用接口
  • 关键点绘制函数和匹配功能绘制函数

【flann】—— Fast Library for Approximate Nearest Neighbors,高维的近似近邻快速搜索算法库
【gpu】——运用GPU加速的计算机视觉模块
【highgui】——也就是high gui,高层GUI图形用户界面,包含媒体的I / O输入输出,视频捕捉、图像和视频的编码解码、图形交互界面的接口等内容
【legacy】——一些已经废弃的代码库,保留下来作为向下兼容
【ml】——Machine Learning,机器学习模块, 基本上是统计模型和分类算法
【video】——视频分析组件,该模块包括运动估计,背景分离,对象跟踪等视频处理相关内容。

更多模块介绍参考:博客1
简单来说,就是opencv打包好了很多有用的库,我们可以调用。
opencv使用入门(c++)
1、图像的表示
一副尺寸为M×N的图像可以表示成M×N的矩阵,矩阵上面的元素的值表示这个位置上面像素的亮度,一般像素址越大表示这个点越亮。
一般来说,灰度图用2维矩阵,彩色(多通道)图像用3维矩阵(M×N×3)表示。对于图像显示来说,现在主流的设备都适用无符号8位整数来(CV_8U)来表示像素亮度。图像在计算机的存储如下:
灰度图:


opencv小白入门教程  第1张





彩色图:

opencv小白入门教程  第2张





在opencv中,RGB图像的通道顺序为BGR,这点需要注意,可以看到彩色图中每列包含依次包含蓝、绿、红三个小列。
2、Mat类
在早期的opencv中,使用Ipllmage和CvMat这个两个数据集表示图像,它们都是c语言的结构,需要自己申请内存,释放内存,比较麻烦。新版本使用了Mat类,新的Mat类能够自己管理内存,Mat接口需要c++支持,Mat的关键属性如下:
class CV_EXPORTS Mat { public: //一系列函数 ... /* flag 参数中包含许多关于矩阵的信息,如: -Mat 的标识 -数据是否连续 -深度 -通道数目 */ int flags; //矩阵的维数,取值应该大于或等于 2 int dims; //矩阵的行数和列数,如果矩阵超过 2 维,这两个变量的值都为-1 int rows, cols; //指向数据的指针 uchar* data; //指向引用计数的指针 //如果数据是由用户分配的,则为 NULL int* refcount; ... }
3、创建Mat对象的几种方式
//使用构造函数 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;
在这里注意:
Mat M(3,2,CV_8SC3,Scalar(0,0,4)); cout<<"M="<<M<<endl;

  • 创造了一个行数(高度)是3,列数(宽度)是2的图像,图像的type是CV_8SC3,表示图像的元素是8位无符号整数,3个通道,图像所有的元素都被初始化位(0,0,255),由于opencv颜色的顺序是BGR,所以这张图片是全红色。
  • 常用的构造函数经常涉及到类型 type。type 可以是 CV_8UC1,CV_16SC1,…, CV_64FC4 等。里面的 8U 表示 8 位无符号整数,16S 表示 16 位有符号整数,64F 表示 64 位浮点数(即 double 类型);C 后面的数表示通道数,例如 C1 表示一个 通道的图像,C4 表示 4 个通道的图像,以此类推。如果为表明通道数,则为如果你需要更多的通道数,需要用宏 CV_8UC(n),例如:Mat M(3,2, CV_8UC(5))
  • Mat中重定义了<<操作符,可以直接输出所有像素值

4、图像矩阵的基本元素表达
对于单通道图像,图像矩阵的元素类型一般是8U,也可以是16S、32F等,这些类型我们可以直接使用uchar、short、float等基本数据类型表达。
对于多通道图像,如RGB彩色图像,需要用三个通道表示,如下图:

opencv小白入门教程  第3张





在这种情况,如果将图像仍然视为一个二维矩阵,每个元素所在位置包含了三个像素值,在Opencv中有模板类Vec,可以表示一个向量。Opencv中使用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;
对于8U类型的RGB彩色图像来说,我们可以使用Vec3b来表示矩阵中元素:
Vec3b color; //用 color 变量描述一种 RGB 颜色 color[0]=255; //B 分量 color[1]=0; //G 分量 color[2]=0; //R 分量
这样对于一个type是8U的RBG3通道彩图,我们就可以仍然将它视为二维矩阵,矩阵每个位置的元素类型为Vec3b。
5、三种像素值读写方式

  • at() 函数

Mat grayim(300,300,CV_8UC1); uchar value=grayim.at<uchar>(i,j); //读出i行j列的像素值 grayim.at<uchar>(i,j)=128; //给i行j列的像素值赋值
可以使用at()函数的方式来对图像进行遍历:
#include <opencv2/core.hpp> #include <iostream> using namespace std; using namespace cv; int main(int argc,char* argv[]){ Mat grayim(600,800,CV_8UC1); Mat colorim(600,800,CV_8UC3); //遍历grayim for(int i=0;i<grayim.rows;i++){ for(int j=0;j<grayim.cols;j++){ grayim.at<uchar>(i,j)=(i+j)%255; } } //遍历colorim for(int i=0;i<colorim.rows;i++){ for(int j=0;j<colorim.cols;j++){ Vec3b pixel; pixel[0]=0; pixel[1]=0; pixel[2]=255; colorim.at<Vec3b>(i,j)=pixel; } } //显示图像 imshow("grayim",grayim); imgshow("colorim",colorim); return 0; }
需要注意的是,如果要遍历图像,并不推荐使用 at()函数。使用这个函数的 优点是代码的可读性高,但是效率并不是很高。

  • 使用迭代器

Mat添加了迭代器的支持,写起来很方便:
#include <opencv2/core.hpp> #include <iostream> using namespace std; int main(int argc,char* argv[]){ cv::Mat grayim(600,600,CV_8UC1); cv::Mat colorim(600,600,CV_8UC3); cv::MatIterator_<uchar> grayit,grayend; cv::MatIterator_<Vec3b> colorit,colorend; //遍历grayimg for(grayit=grayim.begin<uchar>(),grayend=grayim.begin<uchar>;grayit!=grayend;++grayit){ *grayit=rand()%255; } //遍历colorimg for(colorit=colorim.begin<Vec3b>(),colorend=colorim.end<Vec3b>();colorit!=colorend;++colorit){ (*colorit)[0]=rand()%255; (*colorit)[1]=rand()%255; (*colorit)[2]=rand()%255; } cv::imshow("grayit",grayit); cv::imshow("colorit",colorit); return 0; }

  • 使用数据指针

C/C++中的指针操 作是不进行类型以及越界检查的,如果指针访问出错,程序运行时有时候可能看上去一切正常,有时候却突然弹出“段错误”(segment fault)。当程序规模较大,且逻辑复杂时,查找指针错误十分困难。对于不熟悉指针 的编程者来说,指针就如同噩梦。如果你对指针使用没有自信,则不建议直接通 过指针操作来访问像素。虽然 at()函数和迭代器也不能保证对像素访问进行充分 的检查,但是总是比指针操作要可靠一些。
如果你非常注重程序的运行速度,那么遍历像素时,建议使用指针。
#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; }
6、取图像中感兴趣的区域
//提取一行和一列 Mat grayim(600,600,CV_8UC1); firstRow=grayim.row(0); firstCol=grayim.col(0); //使用Range和Rect Mat roi1=grayim(Range(1,3),Range(0,2)); Mat roi2=grayim(Rect(1,0,3,2));
7、读写图片
Mat imread(const string& filename, int flags=1 )
很明显参数 filename 是被读取或者保存的图像文件名;在 imread()函数中, flag 参数值有三种情况:

  • ?ag>0 ,该函数返回 3 通道图像,如果磁盘上的图像?文件是单通道的灰 度图像,则会被强制转为 3 通道;
  • ?ag=0 ,该函数返回单通道图像,如果磁盘的图像?文件是多通道图像,则 会被强制转为单通道;
  • ?ag<0 ,则函数不不对图像进?行行通道转换。

bool imwrite(const string& filename, InputArray image, const vector<int>& params=vector<int>())
文件的格式由 filename 参数指定的文件扩展名确定。推荐使用 PNG 文件格 式。BMP 格式是无损格式,但是一般不进行压缩,文件尺寸非常大;JPEG 格式 的文件娇小,但是 JPEG 是有损压缩,会丢失一些信息。PNG 是无损压缩格式, 推荐使用。
并不是所有的 Mat 对象都可以存为图像文件,目前支持的格式只有 8U 类型 的单通道和 3 通道(颜色顺序为 BGR)矩阵;如果需要要保存 16U 格式图像,只 能使用 PNG、JPEG 2000 和 TIFF 格式。如果希望将其他格式的矩阵保存为图像文 件,可以先用 Mat::convertTo()函数或者 cvtColor()函数将矩阵转为可以保存的格 式。
另外需要注意的是,在保存文件时,如果文件已经存在,imwrite()函数不会 进行提醒,将直接覆盖掉以前的文件。
下面是一个读入图片,做边缘检测,并保存结果。
#include <iostream> #include "opencv2/opencv.hpp" using namespace std; using namespace cv; int main(int argc, char* argv[]){ //读入图片,并转换成单通道 Mat im=imread("./1.jpg",0); //判断是否为空 if (im.empty()){ cout<<"Can not load the image"<<endl; return -1; } Mat result; //边缘检测 Canny(im,result,50,150); //保存图片 imwrite("lena_canny.png",result); }
8、读写视频
介绍 OpenCV 读写视频之前,先介绍一下编解码器(codec)。如果是图像文 件,我们可以根据文件扩展名得知图像的格式。但是此经验并不能推广到视频文 件中。有些 OpenCV 用户会碰到奇怪的问题,都是 avi 视频文件,有的能用 OpenCV 打开,有的不能。
视频的格式主要由压缩算法决定。压缩算法称之为编码器(coder),解压算 法称之为解码器(decoder),编解码算法可以统称为编解码器(codec)。视频文件能读或者写,关键看是否有相应的编解码器。编解码器的种类非常多,常用的 有 MJPG、XVID、DIVX 等,完整的列表请参考 FOURCC 网站 3 。因此视频文件的扩 展名(如 avi 等)往往只能表示这是一个视频文件。
OpenCV 2 中提供了两个类来实现视频的读写。读视频的类是 VideoCapture, 写视频的类是 VideoWriter。

  • 读视频

VideoCapture 既可以从视频文件读取图像,也可以从摄像头读取图像。可以 使用该类的构造函数打开视频文件或者摄像头。如果 VideoCapture 对象已经创 建,也可以使用 VideoCapture::open()打开,VideoCapture::open()函数会自动调用 VideoCapture::release()函数,先释放已经打开的视频,然后再打开新视频。
如果要读一帧,可以使用 VideoCapture::read()函数。VideoCapture 类重载了>> 操作符,实现了读视频帧的功能。下面的例程演示了使用 VideoCapture 类读视 频。
#include <iostream> #include "opencv2/opencv.hpp" using namespace std; using namespace cv; int main(int argc, char** argv){ //打开一个视频 VideoCapture cap("./2.avi"); //检测是否打开 if(!cap.isOpened()){ cout<<"Can not open a camera or file"<<endl; return -1; } Mat edges; //创建窗口 namedWindow("edges",1); for(;;){ Mat frame; //读取一帧 cap>>frame; //判断为空 if(frame.empty()){ break; } //转化为灰度图 cvtColor(frame,edges,CV_BGR2GRAY); //边缘检测 Canny(edges,edges,0,30,3); //显示 imshow("edges",edges); if(waitKey(30)>=0){ break; } } //退出时自动释放cap return 0; }

  • 写视频

使用 OpenCV 创建视频也非常简单,与读视频不同的是,你需要在创建视频 时设置一系列参数,包括:文件名,编解码器,帧率,宽度和高度等。编解码器 使用四个字符表示,可以是 CV_FOURCC(M,J,P,G)、CV_FOURCC(X,V,I,D)及 CV_FOURCC(D,I,V,X)等。如果使用某种编解码器无法创建视频文件,请尝试其他的编解码器。
将图像写入视频可以使用 VideoWriter::write()函数,VideoWriter 类中也重载 了<<操作符,使用起来非常方便。另外需要注意:待写入的图像尺寸必须与创建 视频时指定的尺寸一致。
下面例程演示了如何写视频文件。本例程将生成一个视频文件,视频的第 0 帧上是一个红色的“0”,第 1 帧上是个红色的“1”,以此类推,共 100 帧。生成 视频的播放效果如 所示。
#include <stdio.h> #include <iostream> #include "opencv2/opencv.hpp" using namespace std; using namespace cv; int main(int argc, char** argv){ //视频的高度和宽度 Size s(320,240); //创建writer VideoWriter writer=VideoWriter("myvideo.avi",CV_FOURCC(M,J,P,G),25,s); //判读创建成功没 if(!writer.isOpened()){ cout<<"Can not create video file.\n"; return -1; } //视频帧 Mat frame1(s,CV_8UC3); for(int i=0;i<100;i++){ frame1=Scalar::all(0); char text[128]; snprintf(text,sizeof(text),"%d",i); //将数字画到画面上 putText(frame1,text,Point(s.width/3,s.height/3), FONT_HERSHEY_SCRIPT_SIMPLEX,3, Scalar(0,0,255),3,8); //写入视频中 writer<<frame1; } //退出时自动关闭 return 0; }

The End