1、图像的表示
在计算机看来,图像只是一些亮度各异的点,一副M*N的图片可以用M*N的矩阵来表示,矩阵的值表示这个位置上像素的亮度。
一般灰度图用二维矩阵来表示,彩色(多通道)图用三维矩阵表示,大部分设备都是用无符号8位整数(CV_8U)表示像素的亮度。
2、Mat类
OpenCV中使用Mat类(Matrix的简称)来表示图片,能够自动管理内存。
class CV_EXPORTS Mat
{
public:
//一系列函数
...
/* flag 参数中包含许多关于矩阵的信息,如:
-Mat 的标识
-数据是否连续
-深度
-通道数目
*/
int flags;
//矩阵的维数,取值应该大于或等于 2
int dims;
//矩阵的行数和列数,如果矩阵超过 2 维,这两个变量的值都为-1
int rows, cols;
//指向数据的指针
uchar* data;
//指向引用计数的指针
//如果数据是由用户分配的,则为 NULL
int* refcount; //其他成员变量和成员函数
...
};
使用构造函数来创建Mat对象:
Mat M(,, CV_8UC3, Scalar(,,));
// 创建一个行数为3,列数为2的图像,像素元素是8位无符号整型,初始化为0,0,255(默认颜色顺序为BGR)
cout << "M = " << endl << " " << M << endl;
重新创建对象:
Mat M(,, CV_8UC3);//构造函数创建图像
M.create(,, CV_8UC2);//释放内存重新创建图像,但无法设置像素的初始值
矩阵基本元素表达的话,单通道一般用8U,当然也可以是16S、32F等,这些数据类型可以直接用uchar/short/float等基本数据类型表达。但遇到多通道图形,如RGB彩色图像的时候,依然将图形视为一个二维矩阵,但矩阵的元素不再是基本数据类型,而是使用OpenVC中的模板类Vec表示:
typedef Vec<uchar, > Vec2b;
typedef Vec<uchar, > Vec3b;
typedef Vec<uchar, > Vec4b;
typedef Vec<short, > Vec2s;
typedef Vec<short, > Vec3s;
typedef Vec<short, > Vec4s;
typedef Vec<int, > Vec2i;
typedef Vec<int, > Vec3i;
typedef Vec<int, > Vec4i;
typedef Vec<float, > Vec2f;
typedef Vec<float, > Vec3f;
typedef Vec<float, > Vec4f;
typedef Vec<float, > Vec6f;
typedef Vec<double, > Vec2d;
typedef Vec<double, > Vec3d;
typedef Vec<double, > Vec4d;
typedef Vec<double, > Vec6d; Vec3b color; //用 color 变量描述一种 RGB 颜色
color[]=; //B 分量
color[]=; //G 分量
color[]=; //R 分量
使用at()函数操作矩阵:
Mat grayim(, , CV_8UC1);
Mat colorim(, , CV_8UC3);
//遍历所有像素,并设置像素值
for( int i = ; i < grayim.rows; ++i)
for( int j = ; j < grayim.cols; ++j )
grayim.at<uchar>(i,j) = (i+j)%;
//遍历所有像素,并设置像素值
for( int i = ; i < colorim.rows; ++i) {
for( int j = ; j < colorim.cols; ++j ){
Vec3b pixel;
pixel[] = i%; //Blue
pixel[] = j%; //Green
pixel[] = ; //Red
colorim.at<Vec3b>(i,j) = pixel;
}
}
//显示结果
imshow("grayim", grayim);
imshow("colorim", colorim);
使用迭代器操作矩阵:
Mat grayim(, , CV_8UC1);
Mat colorim(, , CV_8UC3);
//遍历所有像素,并设置像素值
MatIterator_<uchar> grayit, grayend;
for( grayit = grayim.begin<uchar>(), grayend =
grayim.end<uchar>(); grayit != grayend; ++grayit)
*grayit = rand()%;
//遍历所有像素,并设置像素值
MatIterator_<Vec3b> colorit, colorend;
for( colorit = colorim.begin<Vec3b>(), colorend =
colorim.end<Vec3b>(); colorit != colorend; ++colorit)
{
(*colorit)[] = rand()%; //Blue
(*colorit)[] = rand()%; //Green
(*colorit)[] = rand()%; //Red
}
//显示结果
imshow("grayim", grayim);
imshow("colorim", colorim);
waitKey();
使用数据指针操作数据:
// 高效,但是不进行类型以及越界检查
Mat grayim(, , CV_8UC1);
Mat colorim(, , CV_8UC3);
//遍历所有像素,并设置像素值
for( int i = ; i < grayim.rows; ++i)
{
//获取第 i 行首像素指针
uchar * p = grayim.ptr<uchar>(i);
//对第 i 行的每个像素(byte)操作
for( int j = ; j < grayim.cols; ++j )
p[j] = (i+j)%;
}
//遍历所有像素,并设置像素值
for( int i = ; i < colorim.rows; ++i)
{
//获取第 i 行首像素指针
Vec3b * p = colorim.ptr<Vec3b>(i);
for( int j = ; j < colorim.cols; ++j )
{
p[j][] = i%; //Blue
p[j][] = j%; //Green
p[j][] = ; //Red
} }
//显示结果
imshow("grayim", grayim);
imshow("colorim", colorim);
waitKey();
选取图像局部区域
Mat类提供了多钟方便的方法来选择图像局部区域,但这些方法并不进行内存复制操作。如果将图像局部区域赋值给新的Mat对象,新对象和原始对象将共用相同的数据区域。因为不申请内存,所以执行速度都比较快。
1、单行或单列选取
Mat Mat::row(int i) const
Mat Mat::col(int j) const Mat line = A.row(i);
2、多行或多列选择
Range是OpenVC中新增的类,该类有两个关键变量start和end,可以用来表示矩阵的多个连续的行或者列。其表示的范围为从start到end,包含start但不包含end
class Range
{
public:
...
int start, end;
};
//创建一个单位阵
Mat A = Mat::eye(10, 10, CV_32S);
//提取第 1 到 3 列(不包括 3)
Mat B = A(Range::all(), Range(1, 3));
//提取 B 的第 5 至 9 行(不包括 9)
//其实等价于 C = A(Range(5, 9), Range(1, 3))
Mat C = B(Range(5, 9), Range::all());
3、感兴趣的区域(Region of interest)
从图像中可以提取感兴趣的区域,有两种方法
//创建宽度为 320,高度为 240 的 3 通道图像
Mat img(Size(,),CV_8UC3);
//roi 是表示 img 中 Rect(10,10,100,100)区域的对象
Mat roi(img, Rect(,,,));
除了使用构造函数,还可以使用括号运算符,如下:
Mat roi2 = img(Rect(,,,));
当然也可以使用 Range 对象来定义感兴趣区域,如下:
//使用括号运算符
Mat roi3 = img(Range(,),Range(,));
//使用构造函数
Mat roi4(img, Range(10,100),Range(10,100));
4、取对角线元素
Mat Mat::diag(int d) const
参数 d=0 时,表示取主对角线;当参数 d>0 是,表示取主对角线下方的次对
角线,如 d=1 时,表示取主对角线下方,且紧贴主多角线的元素;当参数 d<0 时,
表示取主对角线上方的次对角线。
5、Mat表达式
利用C++运算符重载,代码变的更简洁易懂
下面给出 Mat 表达式所支持的运算。下面的列表中使用 A 和 B 表示 Mat 类
型的对象,使用 s 表示 Scalar 对象,alpha 表示 double 值。
加法,减法,取负:A+B,A-B,A+s,A-s,s+A,s-A,-A
缩放取值范围:A*alpha
矩阵对应元素的乘法和除法: A.mul(B),A/B,alpha/A
矩阵乘法:A*B (注意此处是矩阵乘法,而不是矩阵对应元素相乘)
矩阵转置:A.t()
矩阵求逆和求伪逆:A.inv()
矩阵比较运算:A cmpop B,A cmpop alpha,alpha cmpop A。此处 cmpop
可以是>,>=,==,!=,<=,<。如果条件成立,则结果矩阵(8U 类型矩
阵)的对应元素被置为 ;否则置 。
矩阵位逻辑运算:A logicop B,A logicop s,s logicop A,~A,此处 logicop
可以是&,|和^。 矩阵对应元素的最大值和最小值:min(A, B),min(A, alpha),max(A, B),
max(A, alpha)。
矩阵中元素的绝对值:abs(A)
叉积和点积:A.cross(B),A.dot(B)
6、读图像文件(需要安装对应图像格式的文件格式库)
Mat imread(const string& filename, int flags= ) 、flag>,该函数返回 通道图像
、flag=,该函数返回单通道图像
、flag<,则函数不对图像进行通道转换。
7、写图像文件
bool imwrite(const string& filename, InputArray image,
const vector<int>& params=vector<int>())
// params可以指定文件格式的一些信息
8、读写图片案例
//读入图像,并将之转为单通道图像
Mat im = imread("lena.jpg", );
//请一定检查是否成功读图
if( im.empty() )
{
cout << "Can not load image." << endl;
return -;
}
//进行 Canny 操作,并将结果存于 result
Mat result;
Canny(im, result, , );
//保存结果
imwrite("lena-canny.png", result);
9、读写视频
视频的格式主要是由压缩算法决定,称之为编码器(coder),解压算法称之为解码器(decoder),统称codec。OpenVC使用VideoCapture和VideoWriter来实现视频的读写。
//打开第一个摄像头
//VideoCapture cap(0);
//打开视频文件
VideoCapture cap("video.short.raw.avi");
//检查是否成功打开
if(!cap.isOpened())
{
cerr << "Can not open a camera or file." << endl;
return -1;
}
Mat edges;
//创建窗口
namedWindow("edges",1);
for(;;)
{
Mat frame;
//从 cap 中读一帧,存到 frame
cap >> frame;
//如果未读到图像
if(frame.empty())
break;
//将读到的图像转为灰度图
cvtColor(frame, edges, CV_BGR2GRAY);
//进行边缘提取操作
Canny(edges, edges, 0, 30, 3);
//显示结果
imshow("edges", edges);
//等待 30 秒,如果按键则推出循环
if(waitKey(30) >= 0)
break;
}
//退出时会自动释放 cap 中占用资源
return 0;
//定义视频的宽度和高度
Size s(, );
//创建 writer,并指定 FOURCC 及 FPS 等参数
VideoWriter writer = VideoWriter("myvideo.avi",
CV_FOURCC('M','J','P','G'), , s);
//检查是否成功创建
if(!writer.isOpened())
{
cerr << "Can not create video file.\n" << endl;
return -;
}
//视频帧
Mat frame(s, CV_8UC3);
for(int i = ; i < ; i++)
{
//将图像置为黑色
frame = Scalar::all();
//将整数 i 转为 i 字符串类型
char text[];
snprintf(text, sizeof(text), "%d", i);
//将数字绘到画面上
putText(frame, text, Point(s.width/, s.height/),
FONT_HERSHEY_SCRIPT_SIMPLEX, ,
Scalar(,,), , );
//将图像写入视频
writer << frame;
}
//退出程序时会自动关闭视频文件
return ;