在学习计算机视觉库之前相信大家和我一样都是带有一定的目的性来的,也就是图像,视频的处理。个人有个习惯就是凡事有一个寻找根源的心,确切的讲就是遇到问题去寻找根源然后探索解决方案,这可能也是数学思维带给我的习惯,但我感觉这种习惯是有好处的,可能刚开始进程有些慢,但后面会有意想不到的惊喜。所以国创刚开始我们竟然用了好几天去了解图像在计算机里的显示和存储原理,虽然这些东西都是小儿科,算是常识吧,但是我们还是这样过来了。
图像在计算机中显示是一种什么原理呢?然而图像数据又是以什么样的方式存储的呢?这都是值得思考的问题,只有了解了基本的存储原理,才能进行我们后续的图像处理,各种算法才有用武之地。计算机中最简单的是灰度图,也就是我们平时说的黑白色,数字图像说白了就是一个非常庞大的矩阵,平时我们学的矩阵比如3×3,4×4,7×8等等原理其实是一样的,然而数字图像是一个很庞大的矩阵一般我们见到的都是几千乘几千的,甚至高级的数码相机照出来的图片会更大,相应的存储空间也需要很大。图片是由不同灰度值的像素点构成的矩阵。灰度的不同显示在我们眼中会有颜色的差别从而就成了我们所看到的图像。对于彩色图片来讲,原理其实是相同的,只不过每一个像素点又有不同的通道,也就是RGB(red green blue)三基色,三基色组合就成了我们眼中的彩色图像。想要了解更多的内容可以去google,百度,可笑的是当时我还意外的了解到数码相机的成色原理(什么都不懂的我很容易被一些看似相关的内容吸引过去,看了半天才知道没什么帮助,但算是开了眼界吧)。
上面简单的介绍了一下图像的显示原理,而图像的存储原理其实也是类似的,因为现在的计算机语言可以很容易处理矩阵。但是不同的计算机语言或者不同的库又有不同的实现方法,但总体差别并不是很大。比如matlab中用3维的矩阵来存储彩色图像,用2维的矩阵存储灰度图,OpenCV中原理一样,只不过因为计算机语言本身而有所差别。OpenCV中我们用Mat这一个类来存储我们的数字图像,所以后面所有的内容几乎都离不开Mat。这个内容将在core模块详细讨论,前面我们简单了解一下就好。本篇主要讲OpenCV的基础特性。
在我眼中所谓的OpenCV的基础特性其实好多是C++类的特性。但也有一些是其本身特有的,比如说OpenCV有其特有的像素类型(为了限制template模板的使用)。
特性(1) 自动化的内存管理
OpenCV中vector,Mat,Point等其他基础的结构具有其构析函数,构析函数在需要的时候会释放深层的缓冲区。但是在条件不到的情况下不会释放缓冲区,一直保留缓冲区的数据。其实这样做的目的是为了节省内存空间。这样做其实考虑了数据部分的共享问题,在一个对象被建立的时候会相应的建立数据部分和数据头部,数据的头部用来存储本对象的特征信息,数据部分作为共享,每当引用一次数据部分则引用计数会+1,相应的当且仅当引用计数达到0时数据部分才会被释放。
拿Mat来举例
Mat A(1000, 1000, CV_8UC3);
//创建一个1000×1000的矩阵 同时建立A的头部
Mat B = A;
// 创建另一个B的头部,并且数据部分共享A的数据部分,并不复制A的数据部分
这和我们通常理解的变量赋值略有差异。 有时候我们需要在变量赋值的同时还要复制数据部分,那么这个时候可以使用Mat内部的clone() 函数和copyTo()函数。
特性(2)函数输出向量的空间分配
OpenCV自动释放内存同样也会自动分配内存给函数的输出。如果函数有一个或是多个输入向量和输出向量,那么他会根据菽粟的向量的大小和类型自动给输出向量分配空间或是释放空间。如果有必要,可以在输入的参数表中增加指定输出向量类型的参数下面看一下官方文档中的案例
#include "cv.h"
#include "highgui.h"
using namespace cv;
int main(int, char**)
{
VideoCapture cap(0);
if(!cap.isOpened()) return -1;
Mat frame, edges;
namedWindow("edges",1);
for(;;)
{
cap >> frame;
cvtColor(frame, edges, CV_BGR2GRAY);
GaussianBlur(edges, edges, Size(7,7), 1.5, 1.5);
Canny(edges, edges, 0, 30, 3);
imshow("edges", edges);
if(waitKey(30) >= 0) break;
}
return 0;
}
变量 frame由 >> 自动分配, 变量edges由cvtColor()函数自动分配内存大小和格式。
特性(3)特定像素类型和限制使用模板
C++的模板固然有它的好处,使得程序更加方便。但对于计算机视觉库来说单单一个算法带来的会是成千上万行代码的负担。并且视觉库还要应用到其他语言中例如python,matlab 这些语言并没有template模板,所以很那推广,所以后面版本的OpenCV也要逐渐限制模板的使用。但是考虑到像素的指针操作利用模板速度是相当快的所以部分操作仍然需要模板。
因为模板的限制,结果出现了特定的数据类型集来供视觉库操作,也就是说阵列元素类型被限制为一套特定的数据类型。
下面是一些基础的类型
8-bit unsigned integer (uchar) |
8-bit signed integer(schar) |
16-bit unsigned integer(ushort) |
16-bit signed integer(short) |
32-bit signed integer(int) |
32-bit floating -point number(float) |
64-bit floating-point number(double) |
enum{ CV_8U = 0, CV_8S = 1, CV_16U = 2, CV_16S = 3, CV_32S = 4, CV_32F = 5, CV_64F = 6}
在多通道类型中有下面的形式
CV_<bit-depth>{U|S|F}C(<number_of_channels>)
<>里的内容是解释-必填
{}里的内容任选1
() 里的内容可填可不填
例如 CV_8UC1代表通道数为1的8bit无符号整型 对应上面的基本类型表的uchar
这里CV_8UC1 == CV_8UC; CV_8UC2 == CV_8UC(2) ==CV_MAKETYPE(CV_8U, 2)
CV_MAKETYPE(depth, n) == (depth&7) + ((n - 1)<<3) 这个来自官方文档可是这个是什么意思呢?
其意思是数据类型由通道数确定,通道数减一,然后取最低3位,然后加上下一个log2(CV_CN_MAX)bits.
特性(4) 多线程操作和重用
目前OpenCV的实现完全可以重用。同一个对象的相同函数和相同方法或不同对象的不同非常数方法都可以在不同线程上使用。同样的Mat也 能够在不同线程使用。原因是Mat类的参考计数操作使用了特定的原子指令。(其实这里我挺疑惑它具体怎样实现的,虽然原理明白,什么叫特定的原子指令?)