blueyi's notes

Follow Excellence,Success will chase you!

0%

OpenCV学习小记

本文不会像其他笔记那样详细讲解每一个步骤和问题,最近太忙,没有太多时间,突击学习一下,记录下遇到的一些问题,但依然会像其他笔记一样授之以渔。仅针对C++,OpenCV2,假如你没学习过官方提供详细支持的编程语言,例如C/C++/python,建议还是先学一门编程语言再来学习OpenCV吧。看到网上有教程竟然讲OpenCV时还讲起了C语言。
强烈推荐参考官方手册,官方的doc简直太棒了:http://opencv.org/documentation.html,即能下载PDF,又能在线查看,还有详细的tutorial,以下多数问题的解决来源于查看官方的Reference Manual:http://docs.opencv.org/2.4/opencv2refman.pdf

关于环境配置

环境配置参见这里Windows下OpenCV与VS开发环境配置

一些入门常用函数

2.图像操作及用户界面的几个函数原型及作用(highgui.hpp):
imread:

1
Mat imread(const string& filename, int flags=1 )

用于从文件filename读取图像并在内存中存储为Mat类型,flags用于指宝载入图像使用的色彩类型,如rgb、灰度等

imshow:

1
void imshow(const string& winname, InputArray mat)

在指定的窗口中显示图像mat,由winname指定窗口名称(即窗口标题),如果没有使用namedWindow创建窗口,则新建一个。InputArray类型是一个接口类,可以是Mat、Mat_、Mat_<T, m, n>、vector、vector<vector>、vector类型

imwrite:

1
bool imwrite(const string& filename, InputArray img, const vector<int>& params=vector<int>() )

将图像img保存于指定的文件filname中,最后一个参数于用指定保存的图片格式,如jPEG、PNG等,当不指定最后一个参数时,函数会自动根据finlname中的文件后缀来确定存储格式

createTrackbar:

1
int createTrackbar(const string& trackbarname, const string& winname, int* value, int count, TrackbarCallback onChange=0, void* userdata=0)

创建一个名为trackbarname的滑动条,并将其依附到指定的名为winname的窗口上。第三个参数value用于指定滑动指针的位置,需要是个整型指针变量(因为这个变量会在调节滑动条是发生变化,所以需要是个指针变量);第四个参数count就用来指定滑动条最大可以滑动到多少,例如int value = 50,count = 100,则表示程序运行时滑动指针指向50的位置,最大可以滑动的位置是100。第五个可选参数onChange是一个形如void Foo(int,void*);的回调函数,即当滑动条的value变化时就调用该函数,该回调函数中的第一个参数是滑动条的当前值,即value,第二个参数与createTrackbar的最后一个参数userdata一样,可用于在不使用全局变量的情况下向回调函数传递数据来处理滑动条事件,暂时没有使用过。官方提供的一个使用示例在:opencv_source_code/samples/cpp/connected_components.cpp
注意:该函数创建的滑动条在窗口的顶端,水平放置,且无法改变,如果想改变它的位置,可以通过Qt的GUI来实现

getTrackbarPos:

1
int getTrackbarPos(const string& trackbarname, const string& winname)

返回滑动条的当前位置,其实也就是上面那个函数的value的当前值。

moveWindow

1
void moveWindow(const string& winname, int x, int y)

移动名为winname的窗口

destroyWindow:

1
void destroyWindow(const string& winname)

销毁给定名称的窗体,释放内存

destroyAllWindows:

1
void destroyAllWindows()

销毁所有打开着的HighGUI窗体,并释放内存
这两个函数在小程序时可以省略,因为程序结束运行时会自动被操作系统回收。但对于大程序,应该手动去释放不需要的窗体内存,特别是需要长期运行的程序,以防止内存泄露。

waitKey:

1
int waitKey(int delay=0)

等待按键按下,当延时delay毫秒后依然没有按下,停止等待。如果delay为0或负数时表示一直等待,直到键被按下并返回键值,如果delay时间结束时依然没有键被按下返回-1,该函数可用于函数窗口被关闭,类似C中的getchar()

如何使imshow的显示窗口能够调整大小?
对于高分辨率的图像,很容易显示太大,导致不方便查看,其实只需要在调用imshow之前使用namedWindow创建一个WINDOW_NORMAL的窗体即可,该窗体(官方称为placeholder)可用于放image和trackbar,如果已经有了一个同名的窗体,则该函数调用什么也不干。如果在调用imshow之前已经创建了一个同名window,则imshow会将显示内容置于该window中,如果没有,则imshow会默认创建一个参数为WINDOW_AUTOSIZE的窗体,结合上面的其他图像存取函数举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
using namespace cv;

Mat img;

//滑动条的回调函数,用于根据滑动条的当前值修改窗口大小
static void resizeWin(int size, void*)
{
resizeWindow("rCat", size, size); //将名为rCat的窗口的宽和高都调整为size的值
imshow("rCat", img); //在名为rCat的窗口中显示img中的图像
std::cout << getTrackbarPos("TrackBar", "rCat") << std::endl; //输出名为rCat窗口中的TrackBar滑动条指针所指向的当前值
}
int main(void)
{

int winSize = 80; //定义滑动条指针变量及初始值
img = imread("F:\\cat.jpg", 2 | 4); //cat.jpg是一张高清图像
if(img.empty()) //确保图像打开成功
{
std::cout << "Could not open or find the image" << std::endl;
return -1;
}
namedWindow("Cat", WINDOW_NORMAL); //创建一个名为Cat的窗体,窗口模式设置为NORMAL
imshow("Cat", img); //已经有了名为Cat的窗体,imshow将直接在其中显示img的内容,且是普通窗体,所以imshow显示的内容可以通过鼠标调整大小

namedWindow("rCat", WINDOW_NORMAL); //再创建一个名为rCat的窗体,窗口模式设置为NORMAL
createTrackbar("TrackBar", "rCat", &winSize, 1024, resizeWin); //在名为rCat的窗口上创建一个名为TrackBar的滑动条,滑动条的初始大小为
//为winSize的初始值80,最大值为1024,当winSize改变时,调用回调函数resizeWin
resizeWin(winSize, 0); //回调函数显示结果,如果没有这句,将在程序刚启动时窗口不显示任何内容,但调节滑动条之后就会有了,因为回调函数中设置了显示函数
imwrite("hcat-copy.png", img); //将img中的图像内容写入文件hcat-copy.png,存储格式为png
waitKey(0); //等待键被按下
return 0;
}

播放一个视频
播放视频其实就是读取视频中一帧帧的图像,并将其显示出来,同时将处理后的视频保存,直接看代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <opencv2/opencv.hpp>
#include <iostream>

int main(void)
{
cv::VideoCapture vcap("F:\\bunny.avi"); //创建一个视频对象,可以来源于视频、图像序列或像机
// cv::VideoCapture vcap(0); //表示从默认相机创建一个视频对象vcap
if (!vcap.isOpened()) { //确定文件正常打开
std::cout << "Video or camera open failed!" << std::endl;
return -1;
}
cv::VideoWriter outputVideo;
int ex = static_cast<int>(vcap.get(CV_CAP_PROP_FOURCC)); // Get Codec Type- Int form
cv::Size S = cv::Size((int)vcap.get(CV_CAP_PROP_FRAME_WIDTH), // Acquire input size
(int)vcap.get(CV_CAP_PROP_FRAME_HEIGHT));
outputVideo.open("F:\\bunny_edge.avi", ex, vcap.get(CV_CAP_PROP_FPS), S, false);
cv::Mat edges; //创建一个Mat对象存储图像
cv::namedWindow("Edges", 1); //创建一个名为Edges的窗口
for (;;) {
cv::Mat frame;
vcap >> frame; //将视频中的一帧读入到frame中
cv::cvtColor(frame, edges, CV_BGR2GRAY); //调用灰度化函数处理图像,并存储到edges中
cv::GaussianBlur(edges, edges, cv::Size(7, 7), 1.5, 1.5); //调用高斯模糊函数处理edges中的图像信息
cv::Canny(edges, edges, 0, 30, 3); //调用边缘检测函数来处理edges中图像
cv::imshow("Edges", edges); //将edges中的图像显示到名为Edges的窗口中
outputVideo << edges;
if (cv::waitKey(30) >= 0) break; //延迟30秒后进行一下一帧图像的处理
}
return 0;
}

D:\opencv\sources\samples目录下有很多好玩的实例

核心模块

库的使用,基本的像素操作方法

常用的数据类型和函数

OpenCV中的所有数据类型可以在这里查看:http://docs.opencv.org/2.4.13/modules/core/doc/core.html
1.Mat是Opencv中非常重要的数据类型,它是个很复杂的类型,即可以用于操作二维的图像方阵,又可以作为普通的矩阵类来使用,并且具有自动管理内存的功能,通过上面将视频数据写入到Mat类对象可以看出它应该是被重载了很多的操作符的。所以实际上可以通过一个VideoCapture对象对它进行>>操作,也可以通过cout对它<<操作。
Mat类共有24个构造函数,可见其功能的丰富程度,下面几个常用的构造方式:
直接构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;

int main(void)
{
Mat img(300, 400, CV_8UC3, Scalar(122, 33, 104)); //构造一个300行400列的Mat对象
imshow("Picture", img);

Mat img1(3, 4, CV_8UC3, Scalar(122, 33, 104));
std::cout << img1 << std::endl;
waitKey(0);
destroyWindow("Picture");
return 0;
}

Mat img1(3, 4, CV_8UC3, Scalar(122, 33, 104));
表示构造一个名为img1的二维矩阵对象,该对象由3行4列的Scalar对象构成,可以把这里的Scalar当成是一个vector,其中的三个值分别对应BGR的三个值122,33,104。这三个值构成的颜色刚好是VS2013的LOG颜色。上面第三个参数CV_8UC3是定义好的宏,用于指定RGB的存储属性,意义分别是CV_[The number of bits per item][Signed or Unsigned][Type Prefix]C[The channel number],例子中的意思就是每个像素点使用3通道的8位无符号数字表示(由后面的Scalar来填充)。所以上面的程序运行会输出一个高300像素,宽400像素VS2013的LOGO颜色的纯色框。并且会在命令行中输出3行4列的122,33,104:

1
2
3
[122, 33, 104, 122, 33, 104, 122, 33, 104, 122, 33, 104;
122, 33, 104, 122, 33, 104, 122, 33, 104, 122, 33, 104;
122, 33, 104, 122, 33, 104, 122, 33, 104, 122, 33, 104]

虽然Mat也可以构造为多维的矩阵,但由于imshow和<<运算符只能用于2维或2维以下的Mat对象,所以这里不做解释,可以自行查看这里http://docs.opencv.org/3.0-last-rst/doc/tutorials/core/mat_the_basic_image_container/mat_the_basic_image_container.html#matthebasicimagecontainer

2.通过randu可以使用随机颜色填充Mat对象:

1
2
3
Mat img = Mat(300, 400, CV_8UC3);
randu(img, Scalar::all(0), Scalar::all(255)); //表示填充值从0到255
imshow("Picture", img);

通过create函数构造
3.可以通过调用create(nrows, ncols, type)来进行构造指定size和类型的Mat对象:

1
2
3
4
Mat M(7, 7, CV_32FC2, Scalar(1, 3));  //创建一个7x7的矩阵
M.create(100, 50, CV8UC(15)); //改变其大小和类型,源矩阵会被释放
Mat N;
N.create(M.size(), M.type()); //将N改变为与M一样的大小和类型

创建一个多维矩阵

1
2
3
//创建一个100x100x100的8-bit矩阵
int sz[] = {100, 100, 100};
Mat cube(3, sz, CV_8U, Scalar::all(0));

通过copy函数从一个Mat对象的指定行或列构造另一个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// add the 5-th row, multiplied by 3 to the 3rd row
M.row(3) = M.row(3) + M.row(5)*3;

// now copy the 7-th column to the 1-st column
// M.col(1) = M.col(7); // this will not work
Mat M1 = M.col(1);
M.col(7).copyTo(M1);

// create a new 320x240 image
Mat img(Size(320,240),CV_8UC3);
// select a ROI
Mat roi(img, Rect(10,10,100,100));
// fill the ROI with (0,255,0) (which is green in RGB space);
// the original 320x240 image will be modified
roi = Scalar(0,255,0);

通过数组创建

1
2
3
4
5
6
7
8
9
10
11
//两种情况
void process_video_frame(const unsigned char* pixels,
int width, int height, int step)
{
Mat img(height, width, CV_8UC3, pixels, step);
GaussianBlur(img, img, Size(7,7), 1.5, 1.5);
}

//另一种情况
double m[3][3] = {{a, b, c}, {d, e, f}, {g, h, i}};
Mat M = Mat(3, 3, CV_64F, m).inv();

通过一个逗号分隔的初始化列表创建

1
2
3
4
// create a 3x3 double-precision identity matrix
Mat M = (Mat_<double>(3,3) << 1, 0, 0,
0, 1, 0,
0, 0, 1);

MATLAB风格的zeros(),ones(), eye()函数来初始化

1
2
// create a double-precision identity martix and add it to M.
M += Mat::eye(M.rows, M.cols, CV_64F);

3.opencv内建了多种Mat对象的输出格式,还内建了其他一些支持<<输出的数据类型,官方下面这个例子很全,可以运行查看效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/*  For description look into the help() function. */

#include "opencv2/core/core.hpp"
#include <iostream>

using namespace cv;

static void help()
{
std::cout
<< "\n--------------------------------------------------------------------------" << std::endl
<< "This program shows how to create matrices(cv::Mat) in OpenCV and its serial"
<< " out capabilities" << std::endl
<< "That is, cv::Mat M(...); M.create and std::cout << M. " << std::endl
<< "Shows how output can be formated to OpenCV, python, numpy, csv and C styles." << std::endl
<< "Usage:" << std::endl
<< "./cvout_sample" << std::endl
<< "--------------------------------------------------------------------------" << std::endl
<< std::endl;
}

int main(int, char**)
{
help();
// create by using the constructor
Mat M(2, 2, CV_8UC3, Scalar(0, 0, 255));
std::cout << "M = " << std::endl << " " << M << std::endl << std::endl;

// create by using the create function()
M.create(4, 4, CV_8UC(2));
std::cout << "M = " << std::endl << " " << M << std::endl << std::endl;

// create multidimensional matrices
int sz[3] = { 2, 2, 2 };
Mat L(3, sz, CV_8UC(1), Scalar::all(0));
// Cannot print via operator <<

// Create using MATLAB style eye, ones or zero matrix
Mat E = Mat::eye(4, 4, CV_64F);
std::cout << "E = " << std::endl << " " << E << std::endl << std::endl;

Mat O = Mat::ones(2, 2, CV_32F);
std::cout << "O = " << std::endl << " " << O << std::endl << std::endl;

Mat Z = Mat::zeros(3, 3, CV_8UC1);
std::cout << "Z = " << std::endl << " " << Z << std::endl << std::endl;

// create a 3x3 double-precision identity matrix
Mat C = (Mat_<double>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
std::cout << "C = " << std::endl << " " << C << std::endl << std::endl;

Mat RowClone = C.row(1).clone();
std::cout << "RowClone = " << std::endl << " " << RowClone << std::endl << std::endl;

// Fill a matrix with random values
Mat R = Mat(3, 2, CV_8UC3);
randu(R, Scalar::all(0), Scalar::all(255));

// Demonstrate the output formating options
std::cout << "R (default) = " << std::endl << R << std::endl << std::endl;
std::cout << "R (python) = " << std::endl << format(R, "python") << std::endl << std::endl;
std::cout << "R (numpy) = " << std::endl << format(R, "numpy") << std::endl << std::endl;
std::cout << "R (csv) = " << std::endl << format(R, "csv") << std::endl << std::endl;
std::cout << "R (c) = " << std::endl << format(R, "c") << std::endl << std::endl;

Point2f P(5, 1); //2维点,每一维度由float类型表示
std::cout << "Point (2D) = " << P << std::endl << std::endl;

Point3f P3f(2, 6, 7); //3维点
std::cout << "Point (3D) = " << P3f << std::endl << std::endl;


vector<float> v;
v.push_back((float)CV_PI); v.push_back(2); v.push_back(3.01f);
//下面使用一个包含3个元素的vector构造Mat对象并输出
std::cout << "Vector of floats via Mat = " << Mat(v) << std::endl << std::endl;

//输出以二维点为元素的vector
vector<Point2f> vPoints(20);
for (size_t i = 0; i < vPoints.size(); ++i)
vPoints[i] = Point2f((float)(i * 5), (float)(i % 7));

std::cout << "A vector of 2D Points = " << vPoints << std::endl << std::endl;
return 0;
}

4.Point类用于表示点,Point_<int>Point2iPoint三个一样都表示以int为基础元素类型的点,而Point_<float>Point2f表示以float为基础数据类型的二维上的点:

1
2
3
4
Point p(3, 4);
Point2f pf(3.14, 4.13);
p.x = 5;
p.y = 6;

5.Scalar类可用于表示颜色,它是一个含有4个元素的vector,当用于表示RGB时最后一个元素可以省略,RGB在Scalar中的顺序实际是BGR,即三个元素分量分别对应蓝、绿、红。与.NET中的颜色表示一样。上面的例子中已经使用过了

6.尺寸类Size,有2个参数分别可用于表示二维矩阵的宽、高尺寸,同样具有Size2d,表示以double为基础数据类型,感觉跟Point很像,源码定义也确实在一起。:

1
2
Size si(3, 4);
Size2d sd(3.14, 4.13);

7.矩形类Rect,Rect类有4个成员变量用于表示一个矩形的起点坐标(x, y)和尺寸(width, height),还包括了一些很有用的成员函数,如rec.size()返回Size、area()返回面积、contains(Point)判断点是否在矩形内、inside(Rect)判断另一个矩形是否在该矩形内、tl()返回左上角点坐标、br()返回右下角点坐标。
还被重载了一些非常有用的操作符:

  • rect = rect + point //平移矩形
  • rect = rect - point
  • rect = rect + size //缩放矩形
  • rect = rect - size
  • rect += point, rect -= point, rect += size, rect -= size (简化版操作符)
  • rect = rect1 & rect2 求2个矩形的交集
  • rect = rect1 | rect2 返回包含两个矩形的最小矩形
  • rect &= rect1, rect |= rect1 //简化版操作符
  • rect == rect1, rect != rect1 比较两个矩形

8.颜色空间转换函数cvtColor(),前面已经用过它来转RGB到灰度图。它还可以实现HSV、HSI等色彩模式的转换,函数原型为:

1
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0)

第三个参数是颜色空间转换标识符、第四个参数为目标图像的通道数,0表示取源图像的通道数。

图像表示

1.opencv中的Mat图像以二维矩阵的形式存储,为了效率,有时候会将整个图像在内存中以连续的一行存储,可以通过Mat的成员函数isContinuous()为判断是否为连接存储,如果是,则所有数据在内存中将只以一行的形式存储。
内存中像素在每行中的存储与通道数相关,例如如果是3通道8位无符号,则每个像素应由3个8位无符号组成,即每行中的数字列数实际应该为图像像素列数乘以3。
Mat的一些成员变量或函数:

  • mat.rows 获取总行数,即图片以像素为单位的高度
  • mat.cols 获取总列数,即图片以像素为单位的宽度
  • mat.channels() 获取图像的通道数
  • mat.data 获取图像矩阵的第一行且为第一列的指针,如果返回为空,则表示输入的图像有问题,可以用来判断输入图像是否生效
  • mat.ptr(i) 获取图像第i行的uchar类型的元素指针
  • mat.clone() 返回对象副本

Mat类实现由两部分数据组成:矩阵头(包含矩阵尺寸、存储方法、存储地址等信息)和一个指向存储所有像素值的矩阵的指针。Mat的拷贝构造函数只是拷贝Mat对象的信息头和矩阵指针,而不复制矩阵本身,因为矩阵本身存储的数量量太大。当需要Mat副本时,应该使得cone函数获得副本。

2.缩减像素数
图像当以8位来表示一个单通道的像素点时,可以有2^8=256种色彩值,为了提高处理效率,实际上不需要那么丰富的色彩。此时可以将图像映射为较少色彩值的图像,一般的映射方法为,如果以10为一个颜色值步长的话,可以将0-9全部用0来表示,10-19用10来表示,20-29用20来表示,依次类推。这个步长越大,图像中的颜色值将越少。
可以利用除法的自动截余特性来方便的实现,例如原的来颜色值为old,新值new = (old / step ) * step,step为颜色值的跨度步长。然后遍历所有像素,全部这样转换一遍就获得了缩减之后的图像,转换后由于色彩较少,会有点水彩画的感觉。
但为了提高效率,由于8位一共也就256个值,可以提前建个表,然后当需要转换时从这个表里面读即可,例如:

1
2
3
4
int step = 10;
uchar table[256];
for (int i = 0; i < 256; ++i)
table[i] = (i / step) * step;

这样table里面将是一个缩减后的表,1-9的位置都是0,10-19的位置都是1,依次。。。
当我有个数字26想知道转换后的值是多少时,直接读table[26]的值即可,这样就不用所有图像的每个像素都要进行一次计算了。

而opencv中提供了一个名为LUT的函数,可以直接根据表来实现将一个Mat对象转换为缩减后的Mat对象的功能
LUT的函数原型为:

1
void cv::LUT( cv::InputArray src, cv::InputArray lut, cv::OutputArray dst);

下面分别使用普通方式和LUT方式实现图像缩减,推荐使用LUT函数,效率更高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <opencv2/opencv.hpp>

using namespace cv;

void scanImageAndReduce(Mat &src, uchar *tab); //手动进行缩减

int main(void)
{
Mat img = imread("F:\\cat.jpg");
const int divideWidth = 60; //设定缩减位宽
uchar table[256]; //用于创建一个缩减后的表,以后的颜色取值可以直接从这个表里面取
for (int i = 0; i < 256; ++i) {
table[i] = (uchar)divideWidth * (i / divideWidth); //利用除法的截余特性,即(61/60)*60=1,而(59/60)*60=0
}

Mat img_reduce; //用于存放缩减后的图像
Mat lookUpTable(1, 256, CV_8U); //创建一个1行,256列的Mat对象,其中每个像素用1个通道的8位无符号类型表示

uchar *p = lookUpTable.ptr(); //将原来的table复制到lookUpTable中
for (int i = 0; i < 256; ++i)
p[i] = table[i];

LUT(img, lookUpTable, img_reduce); //LUT函数用于将源图像img,按表lookUpTable转换为缩减后的图像img_reduce

imshow("Original image", img);
imshow("Reduced image by LUT", img_reduce); //显示用LUT缩减后的图像

//LUT函数实现的功能与如下函数类似
Mat img_reduce2 = img.clone(); //复制源图像
scanImageAndReduce(img_reduce2, table);
imshow("Reduced image by manual", img_reduce2); //显示手动缩减后的图像

waitKey(0);

return 0;
}

void scanImageAndReduce(Mat &src, uchar *tab)
{
int channel = src.channels(); //获取源图像的通道数
int nRow = src.rows; //获取源图像的行数和列数,即图像的实际像素高和宽
int nCol = src.cols * channel; //通道数乘以图像的列数即为矩阵中每行的总元素数


if (src.isContinuous()) { //判断图像是否为连续存储的,即所有元素是否都在一行上,为遍历估准备
nCol *= nRow;
nRow = 1;
}

/*
uchar *p; //用于存放从Mat对象中获取的行指针
for (int i = 0; i < nRow; ++i) {
p = src.ptr<uchar>(i);
for (int j = 0; j < nCol; ++j) //遍历各列数值,并将其替换为tab表中指定的值
p[j] = tab[p[j]];
}
*/

//或者直接使用Mat对象的data,data会返回指向矩阵的第一行、第一列的指针。
//如果该指针为空,则表示输入对象无效
uchar *p2 = src.data;
for (int i = 0; i < nCol * nRow; ++i)
*p2++ = tab[*p2];
}

3.统计时间的两个函数,当然C++自带的也可以,举例说明:

1
2
3
4
5
6
double t = getTickCount();  //获取当前CPU走过的时钟周期数

// some method

double time0 = ((double)getTickCount() - t) / getTickFrequency(); //两个时间周期数之差再除以一秒钟CPU所走的时钟周期数,得到时差
std::cout << time0 * 1000 << " ms" << std::endl;

根据每个像素的邻居像素值来修改当前像素实现锐化
原理如下图:

官方示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>

using namespace std;
using namespace cv;

//手动增加掩膜的锐化函数,其实就是将图像中的每一个像素根据他的邻居像素值来进行计算,从而得到一个新的像素
void Sharpen(const Mat& myImage, Mat& Result);

int main(void)
{
const char* filename = "F:\\cat.jpg";

Mat I, J, K; //创建三个Mat对象,I用于存储源图像,J用于存储手动处理的图像,K存储通过内置函数filter2D处理的图像

I = imread(filename);
if (!I.data) { //确保图像载入成功
cout << "Error load image" << endl;
return -1;
}

namedWindow("Input", WINDOW_AUTOSIZE);
namedWindow("Output by Sharpen", WINDOW_AUTOSIZE);

imshow("Input", I);
double t = (double)getTickCount(); //通过2次计时来看看手动写的函数跟使用内置的filter2D哪个速度更快

Sharpen(I, J);

t = ((double)getTickCount() - t) / getTickFrequency();
cout << "Hand written function times passed in seconds: " << t << endl;

imshow("Output by Sharpen", J);

//通过Mat_构造函数来从一个逗号分隔的数字创建一个3x3的Mat对象
Mat kern = (Mat_<char>(3, 3) << 0, -1, 0,
-1, 5, -1,
0, -1, 0);
t = (double)getTickCount();
filter2D(I, K, I.depth(), kern); //调用filter2D,将I中的每个像素与kern相乘,并将结果存放在K中
t = ((double)getTickCount() - t) / getTickFrequency();
cout << "Built-in filter2D time passed in seconds: " << t << endl;

namedWindow("Output by fliter2D", WINDOW_AUTOSIZE);
imshow("Output by fliter2D", K);

waitKey(0);
return 0;
}
void Sharpen(const Mat& myImage, Mat& Result)
{
CV_Assert(myImage.depth() == CV_8U); // accept only uchar images

const int nChannels = myImage.channels(); //读取通道数
Result.create(myImage.size(), myImage.type()); //通过Mat的create将Result调整与源图像同样大小的类型

for (int j = 1; j < myImage.rows - 1; ++j) //对除了边缘行和列之外的所有像素进行处理
{
const uchar* previous = myImage.ptr<uchar>(j - 1); //源图像的当前行的上一行
const uchar* current = myImage.ptr<uchar>(j);
const uchar* next = myImage.ptr<uchar>(j + 1);

uchar* output = Result.ptr<uchar>(j); //指向结果图像的当前行

for (int i = nChannels; i < nChannels*(myImage.cols - 1); ++i) //修改当前行中的每一列,注意多通道时需要通过channel来定位邻居像素位置
{
*output++ = saturate_cast<uchar>(5 * current[i]
- current[i - nChannels] - current[i + nChannels] - previous[i] - next[i]);
}
}

//设置边缘像素为黑色
Result.row(0).setTo(Scalar(0));
Result.row(Result.rows - 1).setTo(Scalar(0));
Result.col(0).setTo(Scalar(0));
Result.col(Result.cols - 1).setTo(Scalar(0));
}

通过上面的运行结果,明显内建函数速度要快很多

其中int Mat::depth() cont返回对象的位深度,例如16-bit signed的图像返回的就是CV_16S。所有位深度的对应信息如下:

  • CV_8U - 8-bit unsigned integers ( 0..255 )
  • CV_8S - 8-bit signed integers ( -128..127 )
  • CV_16U - 16-bit unsigned integers ( 0..65535 )
  • CV_16S - 16-bit signed integers ( -32768..32767 )
  • CV_32S - 32-bit signed integers ( -2147483648..2147483647 )
  • CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )
  • CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )

4.读写感兴趣区域ROI
ROI也即是Region of interest,即针对一副图像中的感兴趣区域进行操作。
由于Mat对象的拷贝构造函数实际上拷贝的是指针,所以可以很方便地从源图像获取感兴趣获取的矩阵到新的Mat对象,而该新的Mat对象实际上关联的是源图像的像素矩阵。有2种方法获取ROI区域,其实就是Mat的两个构造函数:
原型:

1
2
Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange=Range::all() )
Mat::Mat(const Mat& m, const Rect& roi)

举例:

1
2
3
4
5
Mat srcImg;  //认为源图像srcImg已经载入了图像

Mat imgROI = srcImg(Rect(100, 80, ncols, nrows)); //ROI区域为以(80, 100)为起点的ncols x nrows大小的矩阵

Mat imgROI2 = srcImg(Range(100, 100 + nrows), Range(80, 80 + ncols)); //通过提供行和列的范围来指定

使用举例,通过copyTo()函数将一个图像拷贝到另一个图像,copyTo()有一个重载可以附加一个mask来实现隐现效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>

using namespace cv;


int main(void)
{
const char* srcFileName = "F:\\cat.jpg";
const char* logoFileName = "F:\\mini.jpg";

Mat srcImg = imread(srcFileName);
Mat logoImg = imread(logoFileName);
if (!srcImg.data || !logoImg.data) {
std::cout << "File load error \n";
return -1;
}

//创建感兴趣区域,实现上像素矩阵依然是srcImg中的像素值
Mat roiImg = srcImg(Rect(srcImg.rows / 2, srcImg.cols / 2, logoImg.cols, logoImg.rows));

//copyTo的第二个可选参数mask必须为CV_8U类型的图像,并非只能是单通道的灰度图
logoImg.copyTo(roiImg, logoImg);

imshow("ROI mask picture", srcImg);

waitKey(0);
return 0;
}

5.addWeighted线性混合两张图像
官方示例:http://docs.opencv.org/3.0-last-rst/doc/tutorials/core/adding_images/adding_images.html
split()和merge()函数可以实现通道的与合并

6.调整图像对比度与亮度
可以用如下方法初始化一个新Mat对象的所有像素内存为0:

1
Mat new_image = Mat::zeros( image.size(), image.type() );  //image为源图像

官方示例:http://docs.opencv.org/3.0-last-rst/doc/tutorials/core/basic_linear_transform/basic_linear_transform.html
注意其中访问每个像素用的语法:new_image.at<Vec3b>(y, x)[c],其中y是行,x是列,Vec3b就是一个含有3个uchar的vector:typedef Vec<uchar, 3> Vec3b,c表示R、G或B,取值0、1或2。
opencv内建了saturate_cast<type>(value)将value强制转换为有效的type类型的值

7.离散傅里叶变换DFT
DFT即Discrete Fourier Transform,该变换在图像处理中的应用可以做到图像增强与图像去噪、图像分割中的边缘检测、图像特征提取以及图像压缩等。
OpenCV中提供了dft()函数,可以对一维或二维浮点数数组进行正向或反向离散傅里叶变换,可以用于计算两个二维实矩阵的卷积。
http://docs.opencv.org/3.0-last-rst/doc/tutorials/core/discrete_fourier_transform/discrete_fourier_transform.html

8.OpenCV内建对xml和yaml的解析
还是官方文档:
http://docs.opencv.org/3.0-last-rst/doc/tutorials/core/file_input_output_with_xml_yml/file_input_output_with_xml_yml.html
http://docs.opencv.org/2.4.13/modules/core/doc/xml_yaml_persistence.html

9.内建多种图形绘制
http://docs.opencv.org/3.0-last-rst/doc/tutorials/core/basic_geometric_drawing/basic_geometric_drawing.html
直线函数line
函数原型如下:

1
void line(Mat& img, Point pt1, Point pt2, const Scalar& color, int thickness=1, int lineType=8, int shift=0)

thickness就是线宽,lineType就是线的结构,8表示当斜线时两个像素角相连就认为其已经相连了,如果是4则是两个像素有一整条边相连时才算相连。
画箭头可以用arrowedLine

圆circle
函数原型:

1
void circle(Mat& img, Point center, int radius, const Scalar& color, int thickness=1, int lineType=8, int shift=0)

椭圆函数elipse
函数原型:

1
2
void ellipse(Mat& img, Point center, Size axes, double angle, double startAngle, double endAngle, const Scalar& color, int thickness=1, int lineType=8, int shift=0)
void ellipse(Mat& img, const RotatedRect& box, const Scalar& color, int thickness=1, int line- Type=8)

矩形函数rectangle
函数原型:

1
2
void rectangle(Mat& img, Point pt1, Point pt2, const Scalar& color, int thickness=1, int lineType=8, int shift=0)
void rectangle(Mat& img, Rect rec, const Scalar& color, int thickness=1, int lineType=8, int shift=0)

画填充的多边形用fillPoly

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

using namespace cv;

const int backWidth = 400;

int main(void)
{
//定义一个基本图像作为绘图背景
Mat backImg = Mat::zeros(backWidth, backWidth, CV_8UC3);

//设定所绘线的宽度和类型
int thickness = 2;
int lineType = 8;

int radius = 100; //圆半径
Point start = Point(backWidth / 2 - radius, backWidth / 2 - radius); //矩形起点
Point end = Point(backWidth / 2 + radius, backWidth / 2 + radius); //矩形终点

//画矩形
rectangle(backImg, start, end, Scalar(255, 255, 255), thickness, lineType);
//画内接圆
circle(backImg, Point(backWidth / 2, backWidth / 2), radius, Scalar(255, 255, 255), thickness);
//画两条直径
line(backImg, start + Point(0, radius), end - Point(0, radius), Scalar(255, 255, 255), thickness);
line(backImg, start + Point(radius, 0), end - Point(radius, 0), Scalar(255, 255, 255), thickness);

imshow("Draw", backImg);

waitKey(0);
return 0;
}

RNG可以实现产生随机数,putText将窗口中显示文字
http://docs.opencv.org/3.0-last-rst/doc/tutorials/core/random_generator_and_text/random_generator_and_text.html
RNG就是个随机数生成器类,与C++11中的随机数生成器类类似,下面给出这个比较常用的RNG::uniform函数的原型:

1
2
3
int RNG::uniform(int a, int b)
float RNG::uniform(float a, float b)
double RNG::uniform(double a, double b)

putText显示文字
原型:

1
void putText(Mat& img, const string& text, Point org, int fontFace, double fontScale, Scalar color, int thickness=1, int lineType=8, bool bottomLeftOrigin=false )

getTextSize可以返回字符串的大小:

1
Size getTextSize(const string& text, int fontFace, double fontScale, int thickness, int* baseLine)

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//将上例中的图像线条变成彩色并显示文字

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

using namespace cv;

const int backWidth = 400;

//返回随机的颜色
static Scalar randomColor(RNG& rng)
{
int icolor = (unsigned)rng;
return Scalar(icolor & 255, (icolor >> 8) & 255, (icolor >> 16) & 255);
}

int main(void)
{
//定义一个基本图像作为绘图背景
Mat backImg = Mat::zeros(backWidth, backWidth, CV_8UC3);

RNG rng(0xFFFFFFFF); //创建一个RNG对象

//设定所绘线的宽度和类型
int thickness = 2;
int lineType = 8;

int radius = 100; //圆半径
Point start = Point(backWidth / 2 - radius, backWidth / 2 - radius); //矩形起点
Point end = Point(backWidth / 2 + radius, backWidth / 2 + radius); //矩形终点

//画矩形
rectangle(backImg, start, end, randomColor(rng), thickness, lineType);
//画内接圆
circle(backImg, Point(backWidth / 2, backWidth / 2), radius, randomColor(rng), thickness);
//画两条直径
line(backImg, start + Point(0, radius), end - Point(0, radius), randomColor(rng), thickness);
line(backImg, start + Point(radius, 0), end - Point(radius, 0), randomColor(rng), thickness);

//定义随机文字的位置
Point org;
org.x = rng.uniform(10, backWidth / 2);
org.y = rng.uniform(backWidth / 2, backWidth - 10);

//画字体
putText(backImg, "Maxwi.com", org, rng.uniform(0, 8), rng.uniform(0, 100) * 0.05 + 0.1, randomColor(rng), rng.uniform(1, 10), lineType);

imshow("Draw", backImg);

waitKey(0);
return 0;
}

imgproc组件

手动图像处理功能。包括图像的基本处理、变换、轮廓提取、图像分割与修复、直方图与匹配
http://docs.opencv.org/3.0-last-rst/doc/tutorials/imgproc/table_of_content_imgproc/table_of_content_imgproc.html
平滑处理(smoothing)
可以用来对图像降噪,当然也可以模糊图像,是一批带有blur的函数,常用的有以下几个:

  • 方框滤波:boxFilter()
  • 均值滤波(领域平均滤波):blur(),其实是基本的方框滤波
  • 高斯滤波:GaussianBlur()
  • 中值滤波:medianBlur()
  • 双边滤波:bilateraFilter()

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/features2d/features2d.hpp"

using namespace std;
using namespace cv;

/// Global Variables
int DELAY_BLUR = 500;
int MAX_KERNEL_LENGTH = 31;

Mat src;

/// Function headers
int display_dst(const char* winname, const Mat& img, int delay, int i);


/**
* function main
*/
int main(void)
{
Mat dst_blur, dst_ga, dst_me, dst_bi;

/// Load the source image
src = imread("F:\\lena.jpg", 1);
imshow("Original", src);

dst_blur = src.clone();
dst_ga = src.clone();
dst_me = src.clone();
dst_bi = src.clone();

for (int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2)
{
blur(src, dst_blur, Size(i, i), Point(-1, -1));
if (display_dst("blur", dst_blur, DELAY_BLUR, i) != 0) { return 0; }
}

for (int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2)
{
GaussianBlur(src, dst_ga, Size(i, i), 0, 0);
if (display_dst("GaussianBlur", dst_ga, DELAY_BLUR, i) != 0) { return 0; }
}

for (int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2)
{
medianBlur(src, dst_me, i);
if (display_dst("medianBlur", dst_me, DELAY_BLUR, i) != 0) { return 0; }
}

for (int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2)
{
bilateralFilter(src, dst_bi, i, i * 2, i / 2);
if (display_dst("bilateralFilter", dst_bi, DELAY_BLUR, i) != 0) { return 0; }
}

waitKey(0);
return 0;
}

/**
* @function display_dst
*/
int display_dst(const char* winname, const Mat& img, int delay, int i)
{
imshow(winname, img);
if (i < MAX_KERNEL_LENGTH)
waitKey(delay);
else
waitKey(0);
return 0;
}

highgui组件

读写图像及视频文件、如何使用内建的用户界面库
http://docs.opencv.org/3.0-last-rst/doc/tutorials/highgui/table_of_content_highgui/table_of_content_highgui.html

calib3d组件

相机校正及三维信息重建
http://docs.opencv.org/3.0-last-rst/doc/tutorials/calib3d/table_of_content_calib3d/table_of_content_calib3d.html

feature2d组件

主要用于触点检测,边缘检测等
http://docs.opencv.org/3.0-last-rst/doc/tutorials/features2d/table_of_content_features2d/table_of_content_features2d.html

video组件

主要用于对视频流的处理算法,可以进行运动提取、目标跟踪等
http://docs.opencv.org/3.0-last-rst/doc/tutorials/video/table_of_content_video/table_of_content_video.html

objdetect组件

进行目标检测
http://docs.opencv.org/3.0-last-rst/doc/tutorials/objdetect/table_of_content_objdetect/table_of_content_objdetect.html

ml组件:Machine Learning

opencv在机器学习中的使用
http://docs.opencv.org/3.0-last-rst/doc/tutorials/ml/table_of_content_ml/table_of_content_ml.html

photo组件

使用opencv进行高级图像处理
http://docs.opencv.org/3.0-last-rst/doc/tutorials/photo/table_of_content_photo/table_of_content_photo.html

gpu模块

使用GPU加速计算机视觉
http://docs.opencv.org/3.0-last-rst/doc/tutorials/gpu/table_of_content_gpu/table_of_content_gpu.html

opencv在iOS中的应用

http://docs.opencv.org/3.0-last-rst/doc/tutorials/ios/table_of_content_ios/table_of_content_ios.html

OpenCV Viz模块

3D虚拟空间模块
http://docs.opencv.org/3.0-last-rst/doc/tutorials/viz/table_of_content_viz/table_of_content_viz.html

Welcome to my other publishing channels