OpenCV Tutorials — OpenCV 2.4.13.7 documentation

Welcome to opencv documentation! — OpenCV 2.3.2 documentation

OpenCV: Examples

https://github.com/sileixinhua/OpenCV_C-_tutorials

include

1
2
3
4
5
6
7
8
9
#include<iostream>
#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/types_c.h>
#include<string>
#include<vector>
using namespace std;
using namespace cv;

mat矩阵

创建mat矩阵

1
2
3
4
5
cv::Mat mats(500, 400, CV_8U,100);
Matx<double, 5, 3> matrix;
matrix << 1, 2, 3, 4, 5,
6, 7, 8, 9, 10,
11, 12, 13, 14, 15;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//以下为新建mat
void creatMat() {
//mat创建标准矩阵具有同一种类的构造
int rows = 5;
int cols = 5;
//1.1 创建单位矩阵
Mat eyeMat = Mat::eye(rows, cols, CV_8UC1);
//1.2 创建全零矩阵
Size zeroSize = Size_<int>(5, 5);//系统定义的size:typedef Size_<int>Size2i; typedef Size2i Size;
Mat zeroMat = Mat::zeros(zeroSize, CV_8UC1);
//1.3 创建全1矩阵
Mat onesMat = Mat::ones(rows, cols, CV_8UC1);
cout << eyeMat << "\n----\n" << zeroMat << "\n----\n" << onesMat << 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
//利用构造函数创建矩阵
void createMat2() {
int rows = 5;
int cols = 5;
//1.1
Mat mat1(rows, cols, CV_8UC1);
//1.2
Mat mat2(cv::Size(rows, cols), CV_8UC1);
//1.3
Mat mat3(rows, cols, CV_8UC3, Scalar(2, 3, 1));
//1.4
Mat mat4(std::vector<int>(rows, cols), CV_8UC3);
//COPY
Mat mat5(mat3);
//1.6
Mat imageROI(mat5,Rect(10,10,500,400))
}

//先定义后创建
void define_create() {
int rows = 5;
int cols = 5;
Mat mat;
mat.create(rows, cols, CV_8UC3);
Mat mat1;
mat1.create(Size(rows, cols), CV_8UC1);
}

mat3输出结果:

[ 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1;
2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1;
2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1;
2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1;
2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1]

矩阵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
//矩阵初始化
void MatrixInit() {
int rows = 5;
int cols = 5;
//3.1 创建并初始化矩阵
Mat mat8 = (Mat_<uchar>(rows, cols) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
cout << "采用Mat_类构造矩阵\n" << mat8 << endl;
//3.2 构造初始化
Mat mat9(rows, cols, CV_8UC3, Scalar(255));
cout << "构造初始化\n" << mat9 << endl;
//3.3 采用at初始化单通道
Mat mat1(rows, cols, CV_8UC1);
for (int i = 0; i < mat1.rows; i++) {
for (int j = 0; j < mat1.cols; j++) {
mat1.at<uchar>(i, j) = 12;
}
}
cout << "单通道矩阵\n" << mat1 << endl;
//3.4 采用at初始化多通道
Mat mat6(rows, cols, CV_8UC3);
for (int i = 0; i < mat6.rows; i++) {
for (int j = 0; j < mat6.cols; j++) {
mat6.at<cv::Vec3b>(i, j) = cv::Vec3b(1, 3, 2); //Vec<uchar, 3>
}
}


//3.5 采用行指针ptr初始化单通道
for (int i = 0; i < mat1.rows; i++) {
uchar *ptr = mat1.ptr<uchar>(i);
//Vec3b *ptr = mat1.ptr<Vec3b>(i); 多通道
for (int j = 0; j < mat1.cols; j++) {
ptr[j] = 132;
}
}
}

拷贝

1.浅拷贝
B = A
B(A)
这类拷贝方法仅创建了新的矩阵头,共用同一个内存空间,在修改新对象的时候,旧对象也会改变。

2.深拷贝
B = A.clone()
A.copyTo(B)
这类拷贝方法为新的矩阵申请了新的内存空间,在修改新对象的时候,旧对象不会改变。

在此辨析setTo与copyTo

利用setTo()赋值

切记:setTo只能给矩阵赋一个标量值,即第一个参数必须是数值,不能是图像

用法:

1
新图像.setTo(数值,mask);
1
2
3
Mat mat1;
mat1.create(rows,cols,CV_8UC1);
mat1.setTo(5); //全部赋值为5

setTo可以利用mask蒙版

1
2
3
Mat mmat;
mmat.create(rows,cols,CV_8UC1);
mmat.setTo(0, mmat < 85); //mmat<85 生成蒙版,小于85的为0,大于等于85的是255

第二个参数相当于:

1
Mat mask = (mmat<85)

copyTo

1
旧图像.copyTo(新图像,mask);

Mat属性

  • data uchar型的指针。Mat类分为了两个部分:矩阵头和指向矩阵数据部分的指针,data就是指向矩阵数据的指针。
  • dims 矩阵的维度,例如5*6矩阵是二维矩阵,则dims=2,三维矩阵dims=3.
  • rows 矩阵的行数
  • cols 矩阵的列数
  • size 矩阵的大小,size(cols,rows),如果矩阵的维数大于2,则是size(-1,-1)
  • channels 矩阵元素拥有的通道数,例如常见的彩色图像,每一个像素由RGB三部分组成,则channels = 3

下面的几个属性是和Mat中元素的数据类型相关的。

  • type

    表示了矩阵中元素的类型以及矩阵的通道个数,它是一系列的预定义的常量,其命名规则为CV_(位数)+(数据类型)+(通道数)。具体的有以下值:

    | CV_8UC1 | CV_8UC2 | CV_8UC3 | CV_8UC4 |
    | ———— | ———— | ———— | ———— |
    | CV_8SC1 | CV_8SC2 | CV_8SC3 | CV_8SC4 |
    | CV_16UC1 | CV_16UC2 | CV_16UC3 | CV_16UC4 |
    | CV_16SC1 | CV_16SC2 | CV_16SC3 | CV_16SC4 |
    | CV_32SC1 | CV_32SC2 | CV_32SC3 | CV_32SC4 |
    | CV_32FC1 | CV_32FC2 | CV_32FC3 | CV_32FC4 |
    | CV_64FC1 | CV_64FC2 | CV_64FC3 | CV_64FC4 |

    这里U(unsigned integer)表示的是无符号整数,S(signed integer)是有符号整数,F(float)是浮点数。

    例如:CV_16UC2,表示的是元素类型是一个16位的无符号整数,通道为2.

    C1,C2,C3,C4则表示通道是1,2,3,4

    type一般是在创建Mat对象时设定,如果要取得Mat的元素类型,则无需使用type,使用下面的depth

  • depth
    矩阵中元素的一个通道的数据类型,这个值和type是相关的。例如 type为 CV_16SC2,一个2通道的16位的有符号整数。那么,depth则是CV_16S。depth也是一系列的预定义值,
    将type的预定义值去掉通道信息就是depth值:
    CV_8U CV_8S CV_16U CV_16S CV_32S CV_32F CV_64F

图像基本计算

利用函数进行加减乘除等

对于add 函数,只能两个大小、通道数相同的两个矩阵相加

可以添加mask矩阵,mask矩阵中,数值为0的地方,最终输出矩阵的对应元素值为0;数值不为0的地方,最终输出矩阵的对应元素值为两个矩阵对应元素的相加值。

例如下例中,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int Add() {
cv::Mat src1, src2, dst;
int rows = 10;
int cols = 10;

src1.create(rows, cols, CV_8UC1);
src1.setTo(20);
src2.create(rows, cols, CV_8UC1);
src2.setTo(100);
Mat mask(rows, cols, CV_8UC1, Scalar(0));
for (int i = 0; i < mask.rows/2; i++) {
for (int j = 0; j < mask.cols/2; j++) {
mask.at<uchar>(i, j) = 1;
}
}
add(src1, src2, dst, mask);
cout << dst << endl;
return 0;
}

其他的还有:

1
2
3
4
5
6
7
8
9
10
11
void add(InputArray src1, InputArray src2, OutputArray dst,InputArray mask=noArray(), int dtype=-1);//dst = src1 + src2
void subtract(InputArray src1, InputArray src2, OutputArray dst,InputArray mask=noArray(), int dtype=-1);//dst = src1 - src2
void multiply(InputArray src1, InputArray src2,OutputArray dst, double scale=1, int dtype=-1);//dst = scale*src1*src2
void divide(InputArray src1, InputArray src2, OutputArray dst,double scale=1, int dtype=-1);//dst = scale*src1/src2
void divide(double scale, InputArray src2,OutputArray dst, int dtype=-1);//dst = scale/src2
void scaleAdd(InputArray src1, double alpha, InputArray src2, OutputArray dst);//dst = alpha*src1 + src2
void addWeighted(InputArray src1, double alpha, InputArray src2,double beta, double gamma, OutputArray dst, int dtype=-1);//dst = alpha*src1 + beta*src2 + gamma
void sqrt(InputArray src, OutputArray dst);//计算每个矩阵元素的平方根
void pow(InputArray src, double power, OutputArray dst);//src的power次幂
void exp(InputArray src, OutputArray dst);//dst = e**src(**表示指数的意思)
void log(InputArray src, OutputArray dst);//dst = log(abs(src))

重载运算符

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
int learn_operator() {
cv::Mat src1, src2, dst,multiply,add,subtract,divide, divide2, multiply2, add2, subtract2;
int rows = 10;
int cols = 10;

src1.create(rows, cols, CV_8UC1);
src1.setTo(2);
src2.create(rows, cols, CV_8UC1);
src2.setTo(100);
Mat mask(rows, cols, CV_8UC1, Scalar(0));
for (int i = 0; i < mask.rows / 2; i++) {
for (int j = 0; j < mask.cols / 2; j++) {
mask.at<uchar>(i, j) = 1;
}
}
divide = src2 / src1;
divide2 = src2 / 10;
multiply = src1 * 5;
add = src1 + src2;
add2 = src1 + 37;
subtract = src2 - src1;
subtract2 = src2 - 43;

return 0;
}

我们可以看出在opencv中可以运用+-/*,得出一个矩阵。

1
2
3
4
5
Mat rand_mat(rows, cols, CV_8UC1);
randn(rand_mat, 100, 10);
cout << rand_mat << endl;
cout << "--------result------------" << endl;
cout << (rand_mat < 100) << endl;

[100, 102, 93, 96, 112;
97, 94, 103, 105, 89;
108, 100, 113, 80, 81;
95, 93, 119, 103, 86;
115, 92, 75, 85, 86]
————result——————
[ 0, 0, 255, 255, 0;
255, 255, 0, 0, 255;
0, 0, 0, 255, 255;
255, 255, 0, 0, 255;
0, 255, 255, 255, 255]

由此可以看出(rand_mat < 100)的作用可以生成蒙版,这一启示可以帮助我们在setTo等函数中构建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

int DrawRect() {
const char* filename = "D:\\myTemp\\cv\\image\\test.jpg";

cv::Mat mat = cv::imread(filename);
if (mat.empty()) {
throw("Faild open file.");
}

cv::Point p0 = cv::Point(mat.cols / 8, mat.rows / 8);
cv::Point p1 = cv::Point(mat.cols * 7 / 8, mat.rows * 7 / 8);

rectangle(mat, p0, p1, cv::Scalar(0, 255, 0), 5, 8);

cv::Point p2 = cv::Point(mat.cols * 2 / 8, mat.rows * 2 / 8);
cv::Point p3 = cv::Point(mat.cols * 6 / 8, mat.rows * 6 / 8);

rectangle(mat, p2, p3, cv::Scalar(0, 255, 255), 2, 4);
//或者
//rectangle(base, cv::Rect(50, 50, 100, 150), cv::Scalar({ 255,255,255 }), 2,4);
//#第二个参数是矩形的位置和大小,第三个是颜色值,第四个是线条宽度,第五个是线性

cv::imshow("mat", mat);
cv::waitKey();

return 0;
}

绘制圆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int Circles() {
cv::Mat img0(400, 400, CV_8UC3, cv::Scalar(150, 150, 150));
circle(img0, cv::Point(200, 200), 50, cv::Scalar(255, 0, 0)); ///颜色是B G R
/// 第五个参数thickness默认 = 1
cv::imwrite("CirclesImg0.jpg", img0);

cv::Mat img1(400, 400, CV_8UC3, cv::Scalar(150, 150, 150));
circle(img1, cv::Point(200, 200), 100, cv::Scalar(0, 255, 0), 3);
cv::imwrite("CirclesImg1.jpg", img1);

cv::Mat img2(400, 400, CV_8UC3, cv::Scalar(150, 150, 150));
circle(img2, cv::Point(200, 200), 150, cv::Scalar(0, 0, 255), -1);
/// 第五个参数<0,代表内部全部填充
cv::imwrite("CirclesImg2.jpg", img2);

cv::waitKey();
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
int Lines() {
const char* filename = "D:\\myTemp\\cv\\image\\test.jpg";

cv::Mat mat = cv::imread(filename);
if (mat.empty()) {
throw("Faild open file.");
}

int x0 = mat.cols / 4;
int x1 = mat.cols * 3 / 4;
int y0 = mat.rows / 4;
int y1 = mat.rows * 3 / 4;

cv::Point p0 = cv::Point(x0, y0);
cv::Point p1 = cv::Point(x1, y1);
cv::line(mat, p0, p1, cv::Scalar(0, 0, 255), 3, 4);
p0.y = y1;
p1.y = y0;
cv::line(mat, p0, p1, cv::Scalar(255, 0, 0), 3, 4);

cv::imshow("mat", mat);
cv::waitKey();
return 0;
}

添加文字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int DrawText() {
const char* filename = "D:\\myTemp\\cv\\image\\test.jpg";

cv::Mat mat = cv::imread(filename);
if (mat.empty()) {
throw("Faild open file.");
}
cv::Point p = cv::Point(50, mat.rows / 2 - 50);
cv::putText(mat, "Hello OpenCV", p, cv::FONT_HERSHEY_TRIPLEX, 1.5, cv::Scalar(255, 200, 200), 2);
//1.5是缩放比例 p是文字放置点 2是bool值,是否左对齐

cv::imshow("mat", mat);
cv::waitKey();
return 0;
}

简单图像处理

矩阵遍历

取出R G B 各个通道

1
2
3
vector<Mat> channel_mats;
split(mmat, channel_mats);
imshow("mat", channel_mats[0]); //B通道

快速翻转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int Flip() {
const char* filename = "D:\\myTemp\\cv\\image\\test.jpg";

cv::Mat src = cv::imread(filename, IMREAD_GRAYSCALE);
int flipCode = -1; // >0: 沿y-轴翻转, 0: 沿x-轴翻转, <0: x、y轴同时翻转
if (src.empty()) {
throw("Faild open file.");
}
cv::Mat dst;
cv::flip(src, dst, flipCode);

cv::imshow("src", src);
cv::imshow("dst", dst);
cv::waitKey();
return 0;
}

resize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int Resize() {
const char* filename = "D:\\myTemp\\cv\\image\\test.jpg";

cv::Mat src, dst;
float scaleW = 0.8;
float scaleH = scaleW;

src = cv::imread(filename);
if (src.empty()) {
throw("Faild open file.");
}

int width = static_cast<float>(src.cols*scaleW); //强制转换 float转int
int height = static_cast<float>(src.rows*scaleH);
resize(src, dst, cv::Size(width, height));

cv::imshow("src", src);
cv::imshow("dst", dst);
cv::waitKey();
return 0;
}

仿射变换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int Rotate() {
cv::Mat src, dst;

float angle = 90;
const char* filename = "D:\\myTemp\\cv\\image\\test.jpg";

src = cv::imread(filename);
if (src.empty()) {
throw("Faild open file.");
}

cv::Point2f center = cv::Point2f(static_cast<float>(src.cols / 2),
static_cast<float>(src.rows / 2)); // 设置旋转中心
cv::Mat affineTrans = getRotationMatrix2D(center, angle, 0.5);

cv::warpAffine(src, dst, affineTrans, src.size(), cv::INTER_CUBIC, cv::BORDER_REPLICATE);

cv::imshow("src", src);
cv::imshow("dst", dst);
cv::waitKey();
}
INTER_NEAREST 最临近插值算法
INTER_LINEAR 线性插值算法
INTER_CUBIC 双立方插值算法
INTER_AREA 区域插值算法(使用像素区域关系的重采样,时图像抽取的首选方法,但是当图像被放大,它类似于INTER_NEAREST方法)
INTER_LANCZOS4 Lanczos插值(超过8x8邻域的插值算法)
INTER_MAX 用于插值的掩模板
WARP_FILL_OUTLIERS 标志位,用于填充目标图像的像素值,如果其中的一些值对应于原图像中的异常值,那么这些值将被设置为0
WARP_INVERSE_MAP 标志位,反变换

borderMode: 边界像素模式,有默认值BORDER_CONSTANT
. borderValue: 边界取值,有默认值Scalar()即0

这段代码效果:图片缓缓旋转一周

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int RotateCotinue() {
cv::Mat src, dst;
const char* filename = "D:\\myTemp\\cv\\image\\test.jpg";
cv::imread(filename).copyTo(src);
if (src.empty()) {
throw("Faild open file.");
}
cv::Point2f center = cv::Point2f(static_cast<float>(src.cols / 2),
static_cast<float>(src.rows / 2));
cv::imshow("src", src);
cv::namedWindow("dst", cv::WINDOW_AUTOSIZE);
for (float angle = 0.0; angle < 360.0; angle++) {
cv::Mat affineTrans = getRotationMatrix2D(center, angle, 1.0);
cv::warpAffine(src, dst, affineTrans, src.size(), cv::INTER_CUBIC);
cv::imshow("dst", dst);
if (cv::waitKey(1) >= 0)
break;
}
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
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
int Perspective() {
cv::Mat src, dst;
cv::Point2f dstPoint[4];
int xMergin, yMergin;

int pattern = 2;//0,1,2

const char* filename = "D:\\myTemp\\cv\\image\\test.jpg";

cv::imread(filename).copyTo(src);
if (src.empty()) {
throw("Faild open file.");
}

int x0 = src.cols / 4;
int x1 = (src.cols / 4) / 3;
int y0 = src.rows / 4;
int y1 = (src.rows / 4) / 3;
cv::Point2f srcPoint[4] = {
cv::Point(x0,y0),
cv::Point(x0,y1),
cv::Point(x1,y1),
cv::Point(x1,y0),
};

switch (pattern) {
case 0:
xMergin = src.cols / 10;
yMergin = src.rows / 10;
dstPoint[0] = cv::Point(x0 + xMergin, y0 + yMergin);
dstPoint[1] = srcPoint[1];
dstPoint[2] = srcPoint[2];
dstPoint[3] = cv::Point(x1 - xMergin, y0 + yMergin);
break;

case 1:
xMergin = src.cols / 8;
yMergin = src.rows / 8;
dstPoint[0] = srcPoint[0];
dstPoint[1] = srcPoint[1];
dstPoint[2] = cv::Point(x1 - xMergin, y1 - yMergin);
dstPoint[3] = cv::Point(x1 - xMergin, y0 + yMergin);
break;

case 2:
xMergin = src.cols / 6;
yMergin = src.rows / 6;
dstPoint[0] = cv::Point(x0 + xMergin, y0 + yMergin);
dstPoint[1] = srcPoint[1];
dstPoint[2] = cv::Point(x1 - xMergin, y1 - yMergin);
dstPoint[3] = srcPoint[3];
break;
}

cv::Mat perspectiveMmat = cv::getPerspectiveTransform(srcPoint, dstPoint);
cv::warpPerspective(src, dst, perspectiveMmat, src.size(), cv::INTER_CUBIC);

cv::imshow("src", src);
cv::imshow("dst", dst);
cv::waitKey();
}

颜色通道变化

1
2
cv::cvtColor(src, dst, cv::COLOR_RGB2GRAY);   //RGB三通道图片转为灰度图
cvtColor(image,imagehsi, CV_BGR2HSV);

均衡化

1
2
3
cv::imread(filename).copyTo(src);
cvtColor(src, src, COLOR_RGB2GRAY); //切记均衡化务必是单通道
equalizeHist(src, dst);

图像二值化

threshold

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
int Threshold() {
cv::Mat src, dst;
double thresh = 60.0, maxval = 180.0;
int type = cv::THRESH_BINARY;

const char* filename = "D:\\myTemp\\cv\\image\\test.jpg";

cv::imread(filename).copyTo(src);
if (src.empty()) {
throw("Faild open file.");
}

cv::equalizeHist(src,dst);
thresh = 80.0;
maxval = 210.0;
int number = 0;//0,1,2,3,4

switch (number) {
case 0:type = cv::THRESH_BINARY; break;
case 1:type = cv::THRESH_BINARY_INV; break;
case 2:type = cv::THRESH_TRUNC; break;
case 3:type = cv::THRESH_TOZERO; break;
case 4:type = cv::THRESH_TOZERO_INV; break;
}

cv::threshold(src,dst,thresh,maxval,type);
//图像阈值化处理
//第一个参数为原图像
//第二个参数为处理后的输出图像
//第三个参数为阈值
//第四个参数maxval是当灰度值大于(或小于)阈值时将该灰度值赋成的值
//第五个参数为阈值化的状态
}

图像滤波

【OpenCV入门教程之八】线性邻域滤波专场:方框滤波、均值滤波与高斯滤波_【浅墨的游戏编程Blog】毛星云(浅墨)的专栏-CSDN博客_均值滤波与方框滤波

  • 方框滤波——boxblur函数
  • 均值滤波(邻域平均滤波)——blur函数
  • 高斯滤波——GaussianBlur函数
  • 中值滤波——medianBlur函数
  • 双边滤波——bilateralFilter函数

均值滤波

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int Blur() {
cv::Mat src, dst;
const char* filename = "D:\\myTemp\\cv\\image\\test.jpg";

cv::imread(filename).copyTo(src);
if (src.empty()) {
throw("Faild open file.");
}

//主要代码
int ksize = 3;
blur(src, dst, cv::Size(ksize, ksize));
/// Size是卷积核大小,越大越模糊

cv::imshow("src", src);
cv::imshow("dst", dst);
cv::waitKey();
return 0;
}

高斯滤波

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int GaussianBlur() {
cv::Mat src, dst;
const char* filename = "D:\\myTemp\\cv\\image\\test.jpg";

cv::imread(filename).copyTo(src);
if (src.empty()) {
throw("Faild open file.");
}

int ksize1 = 11;
int ksize2 = 11;
double sigma1 = 10.0; //表示高斯核函数在X方向的的标准偏差
double sigma2 = 20.0; //表示高斯核函数在Y方向的的标准偏差。若sigmaY为零,就将它设为sigmaX,如果sigmaX和sigmaY都是0,那么就由ksize.width和ksize.height计算出来。
cv::GaussianBlur(src, dst, cv::Size(ksize1, ksize2), sigma1, sigma2);

cv::imshow("src", src);
cv::imshow("dst", dst);
cv::waitKey();

return 0;
}

卷积算子

以下laplacian、sobel、canny算子常用于边缘检测

Laplacian

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int Laplacian() {
cv::Mat src, dst;
const char* filename = "D:\\myTemp\\cv\\image\\test.jpg";

cv::imread(filename).copyTo(src);
if (src.empty()) {
throw("Faild open file.");
}

Laplacian(src, dst, 0);

cv::imshow("src", src);
cv::imshow("dst", dst);
return 0;
}

sobel算子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int Sobel() {
cv::Mat src, dst;
const char* filename = "D:\\myTemp\\cv\\image\\test.jpg";

cv::imread(filename).copyTo(src);
if (src.empty()) {
throw("Faild open file.");
}

Sobel(src, dst, -1, 0, 1);

cv::imshow("src", src);
cv::imshow("sobel", dst);

return 0;
}

canny算子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int Canny() {
cv::Mat src, dst;
const char* filename = "D:\\myTemp\\cv\\image\\test.jpg";

cv::imread(filename).copyTo(src);
if (src.empty()) {
throw("Faild open file.");
}

double threshold1 = 40.0;
double threshold2 = 200.0;

Canny(src, dst, threshold1, threshold2);

cv::imshow("src", src);
cv::imshow("canny", dst);

return 0;
}

特征提取

角点检测

goodFeaturesToTrack函数

可以计算Harris角点和shi-tomasi角点,但默认情况下计算的是shi-tomasi角点

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
int DetectConers() {
cv::Mat src, gray, dst;
const int maxCorners = 50, blockSize = 3;
const double qualityLevel = 0.01, minDistance = 20.0, k = 0.04;
const bool useHarrisDetector = false;
std::vector< cv::Point2f > corners;

const char* filename = "D:\\myTemp\\cv\\image\\demo.jpg";

cv::imread(filename).copyTo(src);
if (src.empty()) {
throw("Faild open file.");
}

dst = src.clone();
//角点检测必须用灰度图
cvtColor(src, gray, cv::COLOR_RGB2GRAY);
//这个函数用于角点检测
//maxCorners角点数目最大值,如果实际检测的角点超过此值,则只返回前maxCorners个强角点
//qualityLevel:角点的品质因子
//minDistance:对于初选出的角点而言,如果在其周围minDistance范围内存在其他更强角点,则将此角点删除
//cv::Mat():这里是mask,用于提取ROI,这里不需要mask,就用Mat()这样的方式占位
//blockSize:计算协方差矩阵时的窗口大小
//useHarrisDetector:指示是否使用Harris角点检测,如不指定,则计算shi-tomasi角点
//harrisK:Harris角点检测需要的k值

goodFeaturesToTrack(gray, corners, maxCorners, qualityLevel,
minDistance, cv::Mat(), blockSize, useHarrisDetector, k);
//绘制角点
for (size_t i = 0; i < corners.size(); i++) {
circle(dst, corners[i], 8, cv::Scalar(255, 255, 0), 2);
}

cv::imshow("src", src);
cv::imshow("dst", dst);

return 0;
}

==可以学习Harris和shi-tomasi角点检测方法==

Harris角点检测原理详解_lwzkiller的专栏-CSDN博客_harris角点检测

图像形态学处理

形态学,即数学形态学(mathematical Morphology),是图像处理中应用最为广泛的技术之一,主要用于从图像中提取对表达和描绘区域形状有意义的图像分量,使后续的识别工作能够抓住目标对象最为本质〈最具区分能力-most discriminative)的形状特征,如边界和连通区域等。同时像细化、像素化和修剪毛刺等技术也常应用于图像的预处理和后处理中,成为图像增强技术的有力补充。

  • 二值图像的基本形态学运算, 包括腐蚀、膨胀、开和闭。
  • 二值形态学的经典应用, 包括击中击不中变换、边界提取和跟踪、区域填充、提取连通分量、细化和像素化, 以及凸壳
  • 灰度图像的形态学运算, 包括灰度腐蚀、灰度膨胀、灰度开和灰度闭

所有形态学运算都是针对图像中的前景物体进行的, 因而首先对图像前景和背景的认定给出必要的说明.

大多数图像,一般相对于背景而言物体的颜色(灰度)更深, 二值化之后物体会成为黑色, 而背景则成为白色, 因此我们通常是习惯于将物体用黑色(灰度值0)表示, 而背景用白色(灰度值255)表示。 如果有例外,可以先反色处理。

参考了:形态学图像处理_Ricardo的博客-CSDN博客_形态学处理

图像处理中常见的形态学方法 - 知乎 (zhihu.com)

腐蚀与膨胀

数字图像处理—-通俗理解腐蚀与膨胀_alw_123的博客-CSDN博客_图像腐蚀和膨胀的作用

形象来看:膨胀更白,腐蚀更黑

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
int Image_threshold(const Mat inputMat, Mat &outsingleMat) {
Mat dst;
cvtColor(inputMat, dst, COLOR_RGB2GRAY);
imshow("cvtcolor",dst);
threshold(dst, outsingleMat,80,255, cv::THRESH_BINARY);
return 0;
}

//传入的必须是单通道矩阵
int Dilate(Mat &singleMat,Mat &outputArray) {
//dilate(src, dst, cv::Mat());
//第三个参数代表膨胀或腐蚀的核,上面这个就是默认情况:3*3方形核
dilate(singleMat, outputArray, getStructuringElement(MORPH_RECT, Size(3,3)));
return 0;
}
int Erode(Mat &singleMat, Mat &outputArray) {
erode(singleMat, outputArray, getStructuringElement(MORPH_RECT, Size(3, 3)));
return 0;
}
int main() {
Mat mmat, inputMat, outsingleMat;
const char * filename = "D:\\myTemp\\cv\\image\\DSCF1062.jpg";
//首先读取彩色图片
mmat = imread(filename,IMREAD_COLOR);
//彩色图片首先变为灰度图,再变为二值图
Image_threshold(mmat, outsingleMat);
//先腐蚀变黑
Erode(outsingleMat, outsingleMat);
imwrite("erode.jpg", outsingleMat);

//再膨胀
Dilate(outsingleMat, outsingleMat);
imwrite("dilate.jpg", outsingleMat);

//利用蒙版扣原图
Mat dst(mmat.rows, mmat.cols,CV_8UC3);
mmat.copyTo(dst, outsingleMat);
imwrite("erode_dilateS.jpg", dst);

waitKey();
}

视频处理

摄像头数据获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int DispBasic() {
cv::VideoCapture capture(0);

int width = static_cast<int>(capture.get(CAP_PROP_FRAME_WIDTH));//视频流中帧宽度
int height = static_cast<int>(capture.get(CAP_PROP_FRAME_WIDTH));//其他视频流有关的enum可查看源码
std::cout << "frame size = " << width << " * " << height << std::endl;

const char* wName = "camera";
cv::Mat src;
cv::namedWindow(wName, WINDOW_AUTOSIZE);
while (true) {
capture >> src;
cv::imshow(wName, src);
if (cv::waitKey(1) >= 0) {
break;
}
}

return 0;
}

窗口与鼠标

namedWindow

1
namedWindow("myWindow", WINDOW_NORMAL);

参数2:窗口的标识,一般默认为WINDOW_AUTOSIZE 。

WINDOW_AUTOSIZE 窗口大小自动适应图片大小,并且不可手动更改。(上面图1就是使用的它)

WINDOW_NORMAL 用户可以改变这个窗口大小(上面图2就是使用的它)

WINDOW_OPENGL 窗口创建的时候会支持OpenGL

resizeWindow

1
2
3
4
mmat = imread(filename, IMREAD_COLOR);
namedWindow("myWindow", WINDOW_NORMAL);
resizeWindow("myWindow", Size(mmat.cols,mmat.rows));
imshow("myWindow", mmat);

鼠标操作

首先我们可以通过以下例子,掌握鼠标回调函数中 eventflags不同取值的作用,以及setMouseCallback用法

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
86
87
88
89
90
#include<opencv2/opencv.hpp>
#include<iostream>

bool down = false;
int i = 0;

void onMouse(int event, int x, int y, int flags, void* param) //鼠标回调函数
{
/* 鼠标事件发生时,自动执行
Event是整数,表示鼠标事件的类型
X和Y为事件发生时鼠标的坐标,用像素坐标表示
flags表示事件发生时按下鼠标的那个键
param是指向任意对象的指针,作为附件的参数发送给函数

*/

cv::Mat* im = reinterpret_cast<cv::Mat*>(param);
//im事件发生时鼠标所指的图像指针
if (event== cv::EVENT_LBUTTONDOWN) { //如果按下了鼠标左键
std::cerr << "你按下了鼠标左键x=" << x << ", y=" << y << std::endl << std::endl;
down = true;
}
if (event == cv::EVENT_LBUTTONUP) { //如果释放了鼠标左键
std::cerr << "你释放了鼠标左键x=" << x << ", y=" << y << std::endl << std::endl;
down = false;
}
if (event == cv::EVENT_MOUSEMOVE && down==true) { //如果按下左键并移动
std::cerr << "你按下了鼠标左键并移动 x=" << x << ", y=" << y << std::endl << std::endl;
}
/*
cv::EVENT_LBUTTONDOWN=1 左键按下
cv::EVENT_RBUTTONDOWN=2 右键按下
cv::EVENT_MBUTTONDOWN=3 中键按下
cv::EVENT_LBUTTONUP=4 左键放开
cv::EVENT_RBUTTONUP=5 右键放开
cv::EVENT_MBUTTONUP=6 中键放开
cv::EVENT_LBUTTONDBLCLK=7 左键双击
cv::EVENT_RBUTTONDBLCLK=8 右键双击
cv::EVENT_MBUTTONDBLCLK=9 中键双击
cv::EVENT_MOUSEMOVE=0, 鼠标移动
cv::EVENT_MOUSEWHEEL=10, 滚轮滚动
cv::EVENT_MOUSEHWHEEL=11 横向滚轮滚动

*/



if (flags == cv::EVENT_FLAG_LBUTTON) { //左键拖拽
std::cerr << "你拖拽了鼠标左键 x=" << x << ", y=" << y << std::endl << std::endl;
}

if (flags == 8) {
std::cerr << "按住CTRL拖拽 i=" << i++ << std::endl << std::endl;
}


/*
cv::EVENT_FLAG_LBUTTON =1, //左键拖拽
cv::EVENT_FLAG_RBUTTON =2, //右键拖拽
cv::EVENT_FLAG_MBUTTON =4, //中键拖拽
cv::EVENT_FLAG_CTRLKEY =8 //按住CTRL
cv::EVENT_FLAG_SHIFTKEY =16 //按住Shift
cv::EVENT_FLAG_ALTKEY =32 //按住ALT

*/

}


int main(int argc, char** argv) {

cv::Mat image = cv::imread("D:/bb/tu/1.jpg");
if (image.empty()) {
std::cout << "图像读取失败..." << std::endl;
return 0;
}

cv::namedWindow("Original Image");
cv::imshow("Original Image", image);

cv::setMouseCallback("Original Image", onMouse, reinterpret_cast<void*>(&image));//处理鼠标动作
/*
参数1:窗口的名字
参数2:onMouse:鼠标响应函数,回调函数。指定窗口里每次鼠标时间发生的时候,被调用的函数指针
参数3:userdate:传给回调函数的参数【鼠标所指的图像】

*/
cv::waitKey(0);
return 0;
}

示例:交互绘制ROI

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
bool leftButtonPushed = false;
cv::Point startPoint,endPoint;
Mat image,image_copy,imagePartial,imageROI;
void createROI(int event, int x, int y, int flag, void * param) {
if (event == cv::EVENT_LBUTTONDOWN && leftButtonPushed == false) {
leftButtonPushed = true;
startPoint.x = x;
startPoint.y = y;
}
if (leftButtonPushed) {
if (flag == EVENT_FLAG_LBUTTON) {
rectangle(image, Rect(startPoint, Point(x, y)), Scalar(0, 255, 255,80),-1);
}
}
if (event == cv::EVENT_LBUTTONUP) {
endPoint = Point(x, y);
leftButtonPushed = false;
}
imshow("win", image);
}

int main() {
image = cv::imread("D:\\myTemp\\cv\\image\\lena.jpg");
image_copy = image.clone();
if (image.empty()) {
std::cout << "图像读取失败..." << std::endl;
return 0;
}

namedWindow("win");
imshow("win", image);
setMouseCallback("win", createROI, 0);
while (1) {
imshow("win", image);
int c = waitKey(0);
if ((char)c == 'q')
{
destroyAllWindows();
break;
}
}
imageROI.create(image.rows, image.cols, CV_8UC1);
imageROI.setTo(0);
for (int i = startPoint.y; i < endPoint.y; i++) { //由于坐标系是按照
for (int j = startPoint.x; j < endPoint.x; j++) {
imageROI.at<uchar>(i,j) = 255;

}
}
imshow("roi", imageROI);
Mat out;
image_copy.copyTo(out, imageROI);
imshow("new", out);
waitKey();
}

与其他库数据交互

Eigen

头文件必须把Eigen写在前面

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

using namespace std;
using namespace cv;
using namespace Eigen;

void eigen_opencv() {
Eigen::MatrixXd m(2, 5),d;
m << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10;
Mat mat;
eigen2cv(m, mat);
mat = mat + 5;
cv2eigen(mat, d);
}