Lab 1 - OpenCV 的安装和使用

此次实验中,我们将安装OpenCV并熟悉OpenCV的一些基本数据结构。

环境要求

计算摄影学课程假定同学们使用 Windows 10 操作系统并安装有(至少) Visual Studio 2015。
你也可以从 http://ms.zju.edu.cn/ 免费下载 Visual Studio 2015/2017。

OpenCV 的官方网站是 http://opencv.org/,课程推荐使用 OpenCV 3。

同学们可以从https://docs.opencv.org/查阅OpenCV的相关用法。

安装 OpenCV

课程准备了 OpenCV 3.4.5 版的安装程序,另外也准备了可以直接解压缩使用的版本。

你可以运行 opencv-3.4.5-setup.exe,将其解压安装到电脑中。

如果你使用安装程序安装了 OpenCV 3.4.5 ,OpenCV 的安装目录会是如下结构:

Hello, OpenCV

首先我们使用 OpenCV 编写一个简单的 Hello, OpenCV 程序,它将载入一个图片文件,显示在屏幕上。
实验用到的样例图片为 opencv-logo.png。
opencv-logo.png
opencv-logo.png
你需要注意我们如何使用 OpenCV 的头文件,以及链接到它的库文件。

首先,在 Visual C++ 中新建一个空项目,项目名称为 hellocv
New Project
New Project
并在顶端栏目将x86改为x64,因为我们的OpenCV是64位版本的预编译。

代码清单

hellocv 项目中添加一个源代码文件,内容如下:

#include <opencv2/opencv.hpp>
using namespace cv;

int main(int argc, char* argv[]) {
    Mat image = imread("opencv-logo.png"); // 载入名为 "opencv-logo.png" 的图片
    namedWindow("hello"); // 创建一个标题为 "hello" 的窗口
    imshow("hello", image); // 在窗口 "hello" 中显示图片
    waitKey(0); // 等待用户按下键盘
    destroyWindow("hello"); // 销毁窗口 "hello"
    return 0;
}

编译并链接到 OpenCV

打开 hellocv 项目的属性页,首先确保 Configuration (配置)选项处选择了 All Configurations(所有配置),然后在画面左侧选择 VC++ Directories(VC++目录) 子项。在右侧的 Include Directories(包含目录) 下拉项中点选 edit, 参考下图:
Project Properties
Project Properties
在弹出的窗口中添加一个项目,指向 opencv/build/include 的具体路径:
Include Directories
类似的,找到 Library Directories(库目录),并添加对应的库文件路径:

注意如果使用的是 Visual Studio 2015,路径中需要选择 vc14 。你需要选择对应于你环境的路径。

接下来,在左侧找到 Linker(链接器) - Input(输入),右侧的 Additional Dependencies (附加依赖项)中添加需要链接到的 OpenCV 库(.lib)文件:

opencv_world345d.lib

保存改动后,编译程序即可。

Visual Studio在默认情况下使用Debug编译。对应的OpenCV lib库后面带字母d。如果需要更好的性能,切换到Release编译,link的lib库也需要改为opencv_world345.lib。另外,属性页中也可以单独设置Debug和Release的配置(将左上角的所有配置改为Debug或者Release)。

运行带有 OpenCV 的程序

为了运行,应用程序需要能够找到依赖的动态链接库(.dll),否则会提示错误。
这些动态链接库文件位于 lib 目录旁边的 bin 目录中。
Missing DLL
为了运行程序,你可以将需要的opencv/build/vc14_or_15/bin 中的dll 文件(Debug对应opencv_world345d.dll,Release对应opencv_world345.dll)复制到你的程序 exe 所在的目录。
另外,别忘了把OpenCV的logo图像也放到程序的运行目录(通常是.vcxproj所在的目录,你也可以在属性页中自己修改),或者修改程序使用图像的绝对路径。
在一切都配置正常后,你应该可以看到如下的运行结果:
Hello
按下键盘上的任意按键便可退出。

操作 OpenCV 数据

OpenCV 中最常用的数据类型是矩阵 cv::Mat 。在 Hello, OpenCV 的例子里,图片被载入为 cv::Mat 类型。

本次实验课需要你学会阅读 OpenCV 的文档。

任务清单

详解

cv::Mat表示一个矩阵,同时也被用来表示图像。图像可以具有多个色彩通道,为了表示这种情形,Mat的元素可以是特殊的向量类型。

下面的代码构造了一个 32 x 32 的矩阵,它的元素是三通道的,每个通道内容是float类型:

Mat m(32, 32, CV_32FC3);

CV_32FC3代表了这种三通道float类型,32F表示内容是32-bit的浮点数,C3代表三通道。类似地,CV_8UC1表示单通道uchar类型。
对于一个多通道矩阵,它的每个元素是一个向量,向量的维数是矩阵的通道数。OpenCV中使用特殊的Vec类型表示这类向量,CV_32FC3对应的向量类型是Vec3f单通道时,无需使用Vec类型,例如CV_8UC1对应的类型就是uchar

详细的类型可以参考:
http://docs.opencv.org/modules/core/doc/basic_structures.html#datatype

在访问矩阵元素时,可以使用Mat::at<T>方法,T对应于矩阵的元素类型。下面的代码获得到m矩阵第i行第j列处元素的引用:

Vec3f &pix = m.at<Vec3f>(i,j);

矩阵有若干属性,常用的有行数,列数,矩阵的行数可以用m.rows获得,矩阵的列数可以用m.cols
当矩阵用来表示图像时,图像的宽度对应了矩阵的列数,高度则对应了行数。一幅640x480图片可以按照如下方式构造:

Mat img(480, 640, CV_8UC3);

注意长和宽在构造函数中出现的顺序。

下面的代码演示了如何遍历任务中image的元素,将白色区域替换为黑色:

for(int i=0;i<image.rows;++i) {
    for(int j=0;j<image.cols;++j) {
        Vec3b &pixel = image.at<Vec3b>(i,j);
        if(Vec3b(255,255,255)==pixel) {
            pixel = Vec3b(0,0,0);
        }
    }
}

矩阵支持很多基本运算,下面的代码演示了如何构造一幅白色图像,用其减去已有图像来实现图像的反色:

// 构造与原始图像大小相等,类型相同的白色图像
Mat white(image.size(), image.type(), Scalar(255, 255, 255));
// 利用矩阵减法实现反色
image = white - image;

下面的代码完成了本次实验课的最后一个任务,对于一个方阵,Mat::inv() 给出了它的逆:

Mat L = 2.0*Mat::eye(32, 32, CV_32FC1); // 得到主对角线为 2 的矩阵
// 将两条副对角线赋值为 -1
for (int i = 1; i < 32; ++i) {
    L.at<float>(i-1, i) = -1;
    L.at<float>(i, i-1) = -1;
}
Mat Linv = L.inv(); // 计算 L 的逆
Mat result;
normalize(Linv, result, 1.0, 0.0, CV_MINMAX); // 重新映射,使最小值为黑,最大值为白

这里我们使用了很多 OpenCV 提供的便利函数,例如eyenormalize,你也可以利用前面介绍的元素遍历来获得矩阵的最小值和最大值,然后自行完成重映射。

早期的 OpenCV 使用了CvMatIplImage等结构体来表示矩阵和图像等。它们需要使用专门的函数进行建立或销毁,容易产生资源泄漏或悬挂引用等问题。cv::Mat采用C++对象的生命周期来管理它的内容,可以更好的避免这类问题。我们不推荐使用早期的结构体类型。

Tips

参考资料