opencv小白入门教程

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中,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中有模板类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 参数值有三种情况:

  • flag>0 ,该函数返回 3 通道图像,如果磁盘上的图像⽂文件是单通道的灰 度图像,则会被强制转为 3 通道;
  • flag=0 ,该函数返回单通道图像,如果磁盘的图像⽂文件是多通道图像,则 会被强制转为单通道;
  • flag<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;
}

参考博客:

http://blog.csdn.net/llp1992/article/details/50066983

https://wenku.baidu.com/view/f45dc229910ef12d2bf9e7b2.html

http://www.cnblogs.com/yishujun/archive/2014/03/27/3629004.html

http://lib.csdn.net/article/opencv/42000

http://www.cnblogs.com/freeweb/p/5794447.html

http://blog.csdn.net/rznice/article/details/51090966

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容