计算机视觉1->opencv4学习指南1 | 环境配置与例程
opencv虽然很有名,但是自己一直没怎么玩过,暑假的时候使用深度相机做项目,但负责的不是代码模块,也只是配好了环境,没有继续了解图像处理。最近电子实习老师有教这个东西,但是身边不少同学遇到了麻烦,所以在此总结了一下,汇总了一些我行之有效的教程和官方资料,并且附上了两个例程;方便ubuntu环境下的opencv新手快速上手。
00 opencv4.5.3
先放一个链接,当初在ubuntu1804上无痛配置好opencv4.5.3的环境,采用的是
下面是我自己的总结,大家可以避免页面跳转。
00-1 安装相关软件包
1 sudo apt install build-essential 2 3 sudo apt install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev 4 5 sudo apt install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev
如果第三个命令行无法定位软件包;
则
1 sudo add-apt-repository "deb http://security.ubuntu.com/ubuntu xenial-security main" 2 3 sudo apt update 4 5 sudo apt upgrade 6 7 sudo apt install libjasper1 libjasper-dev
libjasper1 是 libjasper-dev 的依赖包
00-2 源码下载
opencv官网下载:
找到自己想要的版本,这里我使用的是4.5.3,暑假的时候还算比较新,现在已经out了。
下载成功后解压,解压到哪个文件夹都可以。
00-3 编译源码
采用cmake的编译方法对下载解压后的软件包进行编译。
进入解压出来的OpenCV-4.5.3文件夹,创建一个新文件夹,我创建的叫 build
进入新文件夹,打开终端,进入这个文件夹
执行命令
1 cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/your_opencv_install_path 2 3 sudo make 4 //为了快也可以直接 sudo make -j3并行编译 5 6 sudo make install
完成后,OpenCV 就安装好了,接下来要配置 OpenCV 的编译环境;
00-4 配置环境
首先将OpenCV的库添加到路径,从而可以让系统找到
打开opencv.conf ,打开后很可能是空白
sudo gedit /etc/ld.so.conf.d/opencv.conf
在文末添加
/usr/local/lib
接下来配置 bash
sudo gedit /etc/bash.bashrc
在文末添加
1 PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig 2 3 export PKG_CONFIG_PATH
保存后退出刷新(source)
1 source /etc/bash.bashrc 2 3 sudo updatedb
配置完成。
01 测试安装成功
转到 opencv-4.5.3/sample/cpp/example_cmake 目录下,打开终端
运行如下命令:
1 cmake . 2 make 3 ./opencv_example
弹出该页面:
然后把电脑自带的摄像头打开,就会出现画面。说明安装、配置成功。
02 读取jpg并显示
02-1 工程建立
首先,在opencv4.5.3文件夹里建立一个我们学习用的文件夹,日后所有的学习项目都会放在这里,方便管理。
mkdir opencv-learn
然后,我们进入这个文件夹
cd opencv-learn
建立今天的第一个项目
1 mkdir learn1 2 cd learn1 3 touch DisplayImage.cpp
02-2 C++程序
编写我们读取jpg并显示的程序
1 #include <stdio.h> 2 #include <opencv2/opencv.hpp> 3 using namespace cv; 4 int main(int argc, char** argv ) 5 { 6 if ( argc != 2 ) 7 { 8 printf("usage: DisplayImage.out <Image_Path>\n"); 9 return -1; 10 } 11 Mat image; 12 image = imread( argv[1], 1 ); 13 if ( !image.data ) 14 { 15 printf("No image data \n"); 16 return -1; 17 } 18 namedWindow("Display Image", WINDOW_AUTOSIZE ); 19 imshow("Display Image", image); 20 waitKey(0); 21 return 0; 22 }
02-3 代码解析
opencv的代码风格与arduino很相似,函数很多,且名字与功能强相关,初入门的编程主要是函数的调用,不涉及复杂数据结构与算法。
前面的if-else是鲁棒性保证,感兴趣可以查一下argv与argc;这里代码的意思是如果命令行编译参数不是2,那么报错。
argv与argc简介如下:
这两个参数在使用命令行编译的时候有用。
argc 为整型,是命令行参数的个数;
argv 是字符串型,为字符串数组,数组成员意义如下:
argv[0]指向程序名;
argv[1]指向程序名后的第一个字符串;
...以此类推
接下来:
Mat image;
定义一个Mat格式的矩阵image用来存储下面会用到的图片。
image = imread( argv[1], 1 );
把读取的图片信息,存进来。
namedWindow("Display Image", WINDOW_AUTOSIZE );
这里是指定展现处理后图片的视窗的名字
1 imshow("Display Image", image); 2 waitKey(0);
展示图片(会弹出一个视窗,名字是上面定义的名字)
-
在显示图片和视频时,会在imshow()时,通常在后面加上while(WaitKey(n)==key),key为大于0的数即可,这样程序将在此处循环运行直到按键响应为key。
-
delay:为0时,则会一直显示这一帧,这在显示摄像头和视频时有用。这个参数用于设置在显示完一帧图像后程序等待“delay”ms后再显示下一帧图片。
-
举几个例子
1 if(waitKey(10)>=0)//是说10ms中按任意键进入if语句 2 3 while(waitKey(2)!=27)//表示不按Esc按键则一直在2ms后显示
02-4 CMakeLists.txt编写
A 内容
接下来我们采用CMakeLists.txt的形式进行编译。
这种形式可以避免引用库不全面而造成的未定义报错。//而这在opencv编程中是经常出现的。
因此CMakeLists.txt是一种很好的编译方式,值得学习。
1 touch CMakeLists.txt 2 3 gedit CMakeLists.txt
在弹出的页面
1 cmake_minimum_required(VERSION 2.8) 2 project( DisplayImage ) 3 find_package( OpenCV REQUIRED ) 4 add_executable( DisplayImage DisplayImage.cpp ) 5 target_link_libraries( DisplayImage ${OpenCV_LIBS} )
B 解释
这里这些语句的意思是:⬇
1.cmake_minimum_required,指定最小需要的版本。里面的内容也是与版本信息有关。
2.project,基本用法是:用于指定cmake工程的名字,比如此处就制定了当前工程的名字是DisplayImage
3.
关于
当我们编译一个需要使用第三方库的程序时,我们需要知道的是:
目标 对照 去哪儿找头文件 .h GCC的 -I 参数 去哪儿找库文件 (.so/.dll/.lib/.dylib/…) GCC的 -L参数 需要链接的库文件的名字 GCC的 -l 参数 比如我们需要一个第三方库 curl,那么我们的 CMakeLists.txt 需要指定头文件目录,和库文件,类似:
1 include_directiories(/usr/include/curl) 2 3 target_link_libraries(myprogram path/curl.so)而如果我们要借助cmake语法的功能,如果借助于cmake提供的finder会怎么样呢?使用cmake的Modules目录下的FindCURL.cmake,相应的CMakeList.txt 文件:
1 find_package(CURL REQUIRED) 2 3 include_directories(${CURL_INCLUDE_DIR}) 4 5 target_link_libraries(curltest ${CURL_LIBRARY})cmake是如何查找这些列出的第三方库的,在这里就不谈了
可以看出我们编写的CMakeList.txt这句的意思是OpenCV的依赖库的添加。
4.add_executable
使用指定的源文件来生成目标可执行文件。这里的目标可执行文件分为三类:
-
普通可执行目标文件
-
导入可执行目标文件
-
别名可执行目标文件
分别对应的三种命令格式如下:
1 add_executable (<name> [WIN32] [MACOSX_BUNDLE] 2 [EXCLUDE_FROM_ALL] 3 [source1] [source2 ...]) 4 5 add_executable (<name> IMPORTED [GLOBAL]) 6 7 add_executable (<name> ALIAS <target>)
这里使用的是最简单的形式:
1 #语法:add_executable(可执行程序名 要编译的cpp)
5.target_link_libraries
该指令的作用为将目标文件与库文件进行链接。
这项的编写需要注意:库之间可能也存在着依赖关系,被依赖的库放在后面。
02-5 编译
编写完成后,我们编译这个项目:
1 cmake . 2 3 make
02-6 执行得到结果
然后我们执行
./DisplayImage TEST.jpg
即可看到C++程序调用opencv处理后显示的图片,虽然在此例子中展示的是图片原貌。
在这个程序基础上可以实现灰度图等等。
03 使用电脑摄像头初步
03-1 工程建立
我们建立一个新的文件夹learn2并建立文件
1 mkdir learn2 2 3 cd learn2 4 5 touch testopencv_camera.cpp
03-2 C++程序
1 //两个功能:如果直接运行程序./exe文件,就是打开电脑摄像头并显示;如果./exe avi视频,那会读取这个avi视频,并在窗口里显示出来。 2 #include "opencv/highgui.h" 3 #include "opencv/cv.h" 4 using namespace std; 5 using namespace cv; 6 7 int main(int argc, char** argv){ 8 cvNamedWindow("testcamera", CV_WINDOW_AUTOSIZE); 9 CvCapture* capture; 10 if (argc == 1){ 11 capture=cvCaptureFromCAM(0); 12 printf("capture 0\n"); 13 } 14 else { 15 capture = cvCreateFileCapture(argv[1]); 16 printf("capture argv1\n"); 17 } 18 assert(capture != NULL); 19 IplImage* frame; 20 frame = cvCreateImage(cvSize(640, 320), IPL_DEPTH_16U, 3); 21 while(1){ 22 frame = cvQueryFrame(capture); 23 if (!frame) 24 break; 25 cvShowImage("testcamera", frame); 26 char c=cvWaitKey(33);//显示间隔是33ms 27 if (c==27)////27ms内按下任意建进入if 28 break; 29 } 30 cvReleaseCapture(&capture); 31 cvDestroyWindow("testcamera"); 32 return 0; 33 } 34
03-3 代码解析
常规库、工作空间准备;
1 #include "opencv/highgui.h" 2 #include "opencv/cv.h" 3 using namespace std; 4 using namespace cv;
接下来,如果我们的命令行编译参数只有一个(argc==1)那么调用cvCaptureFromCAM()函数初始化CvCapture结构的 capture变量。
这是OpenCV库中的一个函数。
初始化从摄像头中获取的视频
CvCapture* cvCaptureFromCAM( int index );
index是个整数;
要使用的摄像头索引。如果只有一个摄像头或者用哪个摄像头也无所谓,那使用参数-1应该便可以。
这个函数用“从摄像头获取的视频流“分配和初始化CvCapture结构。
目前在Windows下可使用两种接口:Video forWindows(VFW)和Matrox Imaging Library(MIL);
Linux下也有两种接口:V4L和FireWire(IEEE1394)。(现在不需要深入了解这个)
释放这个结构,使用函数cvReleaseCapture。
接着,否则的话,我们这个程序会判定我们将使用下面这个功能:
读取一段视频(avi格式)并显示出来;
我们选取参数中的第二个参数(avi视频)读取到capture变量里。
接下来是一个
1 assert(capture != NULL); 2 //意为如果表达式为假,整个程序将推出,不再运行,如果表达式成立,则继续运行。 3 4 //使用assert()的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。
接下来我们要创建一个
1 IplImage* frame;//创建变量 2 3 frame = cvCreateImage(cvSize(640, 320), IPL_DEPTH_16U, 3); 4 //宽为640,高为360,图像像素的位深度为16位无符号整数的图像 5 //尽管在一般IPL图像格式中可以以非交叉的方式存储,并且一些OpenCV可以处理它,但此函数只能创建交叉存储的图像。
函数cvCreateImage创建图像首地址,并分配存储空间。
IplImage* cvCreateImage(CvSize cvSize(int width, int height), int depth, int channels);
接下来进入循环
摄像头或者文件中抓取并解压返回这一帧
frame = cvQueryFrame(capture);
这个函数:
1 IplImage* cvQueryFrame( CvCapture* capture );
这个函数仅仅是函数cvGrabFrame和函数cvRetrieveFrame在一起调用的组合。
返回的图像不可以被用户释放或者修改。
抓取后,capture被指向下一帧,可用cvSetCaptureProperty调整capture到合适的帧。
如果图像存储空间没有批下来,则退出循环;
1 if (!frame)、 2 break;
批下来了,就展示出来视频的一帧frame
cvShowImage("testcamera", frame);
下面是退出程序的控制程序;
1 char c=cvWaitKey(33); 2 if (c==27)//27ms 3 break; 4 } 5 cvReleaseCapture(&capture); 6 cvDestroyWindow("testcamera"); 7 return 0;
03-4 编译运行结果
1 # 编译 2 g++ testopencv_camera.cpp -o first `pkg-config --libs --cflags opencv` -ldl 3 #运行 4 ./first 5 #就可以看到电脑摄像头显示的"视频"