1 概述 – OpenCV介绍与环境搭建
- HighGUI部分
- Image Process
- 2D Feature
- Camera Calibration and 3D reconstruction
- Video Analysis
- Object Detection
- Machine Learning
- GPU加速
2 加载、修改、保存图像
1 加载图像(用cv::imread) 2 修改图像(cv::cvtColor) 3 保存图像(cv::imwrite) 4 代码演示
(1)加载图像(用cv::imread)
- imread功能是加载图像文件称为一个Mat对象(类对象)(源码中Mat类有多个重载的构造函数[参数表不同])
- cv::Mat cv::imread(const cv::String &filename, int flags = 1)
- 其中第一个参数表示图像文件名称(文件的绝对地址)
- 第二个参数,表示加载的图像是什么类型,常见的有三个参数值(都是
enum
枚举类型)- IMREAD_UNCHANGED(<0)表示加载原图,不做任何改变
- IMREAD_GRAYSCALE(0)表示把原图作为灰度图像加载进来
- IMREAD_COLOR(>0)表示把原图作为RGB图像加载进来
注意:OpenCV支持JPG、PNG、TIFF等常见格式图像文件加载。
(2)显示图像(cv::namedWindow与cv::imshow)
namedWindow
功能是创建一个OpenCV窗口,它是由OpenCV自动创建与释放的无需自己销毁(早年需要destroyWindow)- 常见用法
namedWindow("Window Title", WINDOW_AUTOSIZE)
WINDOW_AUTOSIZE
会自动根据图像大小,显示窗口大小,不能人为改变窗口大小WINDOW_NORMAL
,跟QT集成的时候会使用,允许修改窗口大小imshow
根据窗口名称显示图像到指定的窗口上去,第一个参数是窗口名称,第二个参数是Mat对象
(3)修改图像(cv::cvtColor)
- cvtColor的功能是把图像从一个色彩空间转换到另一个色彩空间。调整亮度/饱和度时先转换空间,改变效果后,再转换回来。有三个参数
- 表示源图像
- 表示色彩空间转换之后的图像
- 表示源和目标色彩空间
COLOR_BGR2HLS
(L->Light[亮度],S->saturation[饱和度])COLOR_BGR2GRAY
(BGR=>RGB,原因是B通道在前)COLOR_BGR2HSV
(S->saturation[饱和度])
cvtColor(image, gray_image, COLOR_BGR2GRAY)
- (image, gray_image都是Mat类对象)
(4)保存图像(cv::imwrite)
- 保存图像到指定目标路径(?路径中一定要加文件名.后缀名)
- 只有8位、16位的PNG、JPG、Tiff文件格式而且是单通道或者三通到的BGR图像才可以通过这种方式保存
- 保存PNG格式的时候可以保存透明通道的图片
- 可以指定压缩参数
3 矩阵的掩膜操作
1 获取图像像素指针 2 掩膜操作解释 3 代码演示
(1)获取图像像素指针
CV_Assert(myImage.depth() == CV_8U);
测试位图深度是否为8位,如果False则停止运行- 位图深度:在灰度图像中8位代表从黑(0)到白(1)分为256个颜色深度;在RGB图像中代表每个通道(每个原色)都分为256个子色,总体上为256 * 256 * 256种颜色
Mat.ptr<uchar>(int i = 0)
(uchar是unsigned char,一个字节0~ 255,像素值也是在0~255)获取像素矩阵的指针,索引i表示第几行,从0开始计行数- 获得当前行指针
const uchar* current = myImage.ptr<uchar>(row);
- 获取当前像素点P(row, col)的像素值
p(row, col) = current[col];
(2)像素范围处理saturate_cast
saturate_cast<uchar>(-100)
,返回0saturate_cast<uchar>(288)
,返回255saturate_cast<uchar>(124)
,返回124- 这个函数的功能是确保RGB值的范围在0~255之间
(3)掩膜操作解释
代码示例:(RGB图像可以理解为一个像素占三个字节(R/G/B))
这是一张3*3
的(255, 0, 0)的纯红色图片 (.ptr<uchar>(int)
是Mat类的成员函数,uchar用于表示像素0~255)
//示例中使用
Mat resultImage;
myImage.copyTo(resultImage); //将图片clone
//视频中
Mat resultImage;
resultImage = Mat::zeros(myImage.size(), myImage.type());
//clone了新对象,zeros产生一个纯黑的图像,拷贝图像大小和类型
(4)函数调用filter2D功能
- 定义掩膜:
Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
- Mat_是一个类模版,是对Mat的封装没有添加更多的属性。char是typename,
Mat_(int _rows, int _cols)
是它的一个构造函数。目的是避免多处声明数据类型导致出错
filter2D(src, dst, src.depth(), kernel);
- 其中src与dst是Mat类型变量、src.depth表示位图深度,有32、24、8等。
4 Mat对象
1 Mat对象与IplImage对象 2 Mat对象使用 3 Mat定义数组
(1)Mat对象与IplImage对象
- Mat对象OpenCV2.0之后引进的图像数据结构、自动分配内存、不存在内存泄漏的问题,是面向对象的数据结构。分头部与数据部分
- IplImage
- 是从2001年OpenCV发布之后就一直存在,是C语言风格的数据结构,需要开发者自己分配与管理内存,对大的程序使用它容易导致内存泄漏问题?
(2)Mat对象构造函数与常用方法
对象构造函数
Mat()
Mat(int rows, int cols, int type)
传入长宽像素大小,和矩阵单位类型- 解释一下type矩阵类型
- 可以通过原对象.convertTo(目标Mat对象, 目标类型)来改变
CV_8UC1
(灰度)、CV_8UC2
、CV_8UC3
(彩色BGR)、CV_8UC4
(带透明色的BGR)都是Unsigned 8bitsCV_32FC1
、CV_32FC2
、CV_32FC3
是float32位CV_64FC1
、CV_64FC2
、CV_64FC3
是double64位
- 解释一下type矩阵类型
Mat(Size size, int type)
传入另一个Mat对象的.size()Mat(int rows, int cols, int type, const Scalar &s)
传入长宽,类型和Scalar像素值(Scalar(B, G, R))- Mat M(24, 24, CV_8UC3, Scalar(0, 0, 255))
-
- 其中前两个参数分别为行(row)和列(column),第三个CV_8UC3中 8表示每个通道占8位、U表示无符号、C表示Char类型、3表示通道数目是3,第四个参数是向量表示初始化每个像素值是多少,向量长度对应通道数目一致
Mat(Size size, int type, const Scalar &s)
Mat(int ndims, const int *sizes, int type)
Mat(int ndims, const int *sizes, int type, const Scalar &s)
(3)常用方法
void copyTo(Mat dst)
拷贝Mat对象。用法(拷贝到):Mat对象名.copyTo(Mat对象名);void convertTo(Mat dst, int type)
转换Mat矩阵类型Mat clone()
完全克隆。用法:Mat对象名 = Mat对象名.clone();int channels()
返回通道数int depth()
返回位图深度bool empty()
返回是否为空对象uchar* ptr(i=0)
返回一个指向行的指针。说明:default=0是第一行的索引Mat::zeros(int rows, int cols, int type)
返回一个Mat对象,像素值都为0Mat::eye(int rows, int cols, int type)
返回一个Mat对象,主对角线上像素值都为1,其他像素为0
Mat dst = Scalar( , , )
括号内填入像素值,生成一个确定BGR像素的图片。若范围在0~255,则代表位图深度为8,RGB每个原色都分成255个子色。
(4)Mat对象使用
- 部分复制:一般情况下只会复制Mat对象的头和指针部分,不会复制数据部分
Mat A = imread(imgFilePath);
Mat B(A);
- 完全复制:如果想把Mat对象的头部和数据部分一起复制,可以通过如下两个API实现
Mat B = A.clone()
Mat G; A.copyTo(G);
Mat定义数组:Mat kernel = (Mat_<typename>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
5 图像操作
1 读写图像 2 读写像素 3 修改像素值
(1)读写像素
- 读一个GRAY像素点的像素值(CV_8UC1)
int intensity = img.at<uchar>(row, col);
int intensity = img.at<uchar>(Point(row, col));
- 读一个RGB像素点的像素值(CV_8UC3)
Vec3f intensity = img.at<Vec3b>(row, col);
Vec3b -> 返回值是unsigned charint blue = intensity.val[0];
int blue = img.at<Vec3b>(row, col)[0];
这种写法也是对的 blueint green = intensity.val[1];
greenint red = intensity.val[2];
red
.ptr(row)[col]和.at(row, col)[channel]的区别:
.ptr<uchar>(row)[col]
是在一个已展开的图像上提取像素指针,对于多通道而言(BGR三通道ptr获取像素指针时,每个位置三个通道是排开的,可以理解为一个像素位置由三个字节构成)- 它的返回值类型为
const _Tp * ptr<_Tp, n>(const cv::Vec<int, n> &idx) const
- 它的返回值类型为
.at<uchar>(row, col)[channel]
是在原图像位置上取出像素指针,通过[channel]获取不同通道下的像素指针的- 它的返回值类型为
const _Tp &at<_Tp>(cv::Point pt) const
- 它的返回值类型为
对各通道取反操作API bitwise_not(input_img, output_img);
编译器快捷键 选中代码后Alt + 方向键
移动代码块
6 图像混合
1 理论-线性混合操作 2 相关API(addWeighted) 3 代码演示
(1)理论-线性混合操作
(2)相关API(addWeighted)
void cv::addWeighted ( InputArray src1,
double alpha,
InputArray src2,
double beta,
double gamma,
OutputArray dst,
int dtype = -1
)
- 参数1:输入图像Mat-src1
- 参数2:输入图像src1的α\alpha值
- 参数3:输入图像Mat-src2
- 参数4:输入图像src2的α\alpha值
- 参数5:γ\gamma(gamma)值
- 参数6:输出混合图像
注意点: 两张图像的大小和类型必须一致才可以。 四个函数:
void add(cv::InputArray src1, cv::InputArray src2, cv::OutputArray dst, cv::InputArray mask = noArray(), int dtype = -1)
像素直接相加函数void multiply(cv::InputArray src1, cv::InputArray src2, cv::OutputArray dst, double scale = (1.0), int dtype = -1)
像素直接相乘函数int64 cv::getTickCount()
用于返回从操作系统启动到当前所经的计时周期数- int64=>long long是64位整数,类似的有int16=>short,int32=>int
double cv::getTickFrequency()
用于返回CPU的频率(1s内的计时周期数)- 用来计算当前程序的运行时间
7 调整图像亮度与对比度
1 理论 2 代码演示
(1)理论
图像变换:
- 像素变换 — 点操作
- 邻域操作 — 区域
调整图像亮度和对比度属于像素变换。 g(i,j)=αf(i,j)+β(g(i,j) 其中α>0, β 是增益变量 α 控制对比度,β 控制亮度。 小知识点:int64
类型是 long long
,size_t
类型是 unsigned long long
8 绘制形状与文字
1 使用
cv::Point
与cv::Scalar
2 绘制线、矩形、圆、椭圆等几本几何形状 3 随机生成与绘制文本 4 代码演示
(1)使用cv::Point与cv::Scalar
- Point表示2D平面上一个点(x, y)
- Scalar表示至多四个元素的向量
Point和Scalar都是类模板
//Scalar源码types.hpp
typedef Scalar_<double> Scalar;
//Scalar_源码
template<typename _Tp> class Scalar_ : public Vec<_Tp, 4>
//Vec源码
template<typename _Tp, int cn> class Vec : public Matx<_Tp, cn, 1>
//Matx源码
template<typename _Tp, int m, int n> class Matx
- 关于OpenCV中矩阵
Matx
和退化的单行矩阵Vec
源码分析可以参考Ph.D, HUST.的OpenCV源码阅读之matx.h
(2)绘制线、矩形、圆、椭圆等几本几何形状
提醒: 当线宽设置为-1时,会填充整个形状
- 画线
cv::line
(线的类型:LINE_4\LINE_8\LINE_AA(反锯齿))
void Draw_Line(int line_width, Mat& tmp)
{
line(tmp, Point(100, 100), Point(200, 200), Scalar(0, 0, 255), line_width, LINE_8);
}
- 画椭圆
cv::ellipse
void ellipse(InputOutputArray img, Point center, Size axes, double angle, double startAngle, double endAngle, const Scalar& color, int thickness = 1, int lineType =8, int shift = 0)
Point
圆心坐标Size
椭圆长轴、短轴长度angle
初始旋转角度startAngle
椭圆起始角度endAngle
椭圆终止角度(这两个用于画椭圆弧)
void ellipse(int line_width, Mat& tmp)
{
ellipse(tmp, Point(tmp.rows / 2, tmp.cols / 2), Size(tmp.rows / 4, tmp.cols / 8), 45, 0, 360, Scalar(150, 150, 0), line_width, LINE_8);
}
画矩形 cv::rectangle
Rect
用来创建一个矩形对象。构造函数:Rect::Rect()
默认构造函数,矩形左上角的横纵坐标,矩形大小均为0Rect::Rect(point&, size&)
用Point对象初始化矩形左上角的横纵坐标;用**Size(width, height)**初始化矩形大小Rect::Rect(INT, INT, INT, INT)
用四个证书初始化角点的横纵坐标、矩形大小
Rect rect = Rect(100, 100, 200, 200);
//Rect rect(Point(100, 100), Size(200, 200));
void rectangle(int line_width, Mat& tmp)
{
rectangle(tmp, rect, Scalar(0, 255, 0), line_width, LINE_8);
}
- 画圆
cv::circle
//和椭圆很相似,Size->radius半径
void circle(int line_width, Mat& tmp)
{
circle(tmp, Point(tmp.rows / 2, tmp.cols / 2), 50, Scalar(0, 125, 125), line_width, LINE_8);
}
- 画填充
cv::fillPoly
void fillPoly(InputOutputArray img, const Point **pts, const int *npts, int ncontours, const Scalar &color, int lineType = 8)
Point ** pts
用来接指向Point数组的指针int *npts
用来接一共有几个指向顶点位置的指针,实际顶点个数+1(最后一个指回初始点)int ncontours
是几个闭环,是point[1][6]中第一维的数值
void Polygon(Mat& tmp)
{
//定义一个五边形的五个顶点,必须用数组
const Point point[1][6];
point[0][0] = Point(0, 0);
point[0][1] = Point(100, 100);
point[0][2] = Point(100, 50);
point[0][3] = Point(80, 30);
point[0][4] = Point(50, 10);
point[0][5] = Point(0, 0);
const Point* pts[] = { point[0] };
int npts[] = { 6 }; //定义成数组的原因是数组名是首地址,可以直接传给指针
fillPoly(tmp, pts, npts, 1, Scalar(100, 0, 0), 8);
}
- 文字
putText
void putText(InputOutputArray img, const String& text, Point org, int fontFace, double frontScale, Scalar color, int thickness, int lineType)
fontFace
代表字体格式,如:CV_FONT_HERSHEY_COMPLAX
CV_FONT_BLACK
putTest(img, "content", Point(img.rows / 2, img.cols / 2), CV_FONT_HERSHEY_COMPLAX, 1.0, Scalar(12, 24, 200), 3, 8);
这里再次提一下waitKey函数
- waitKey函数是一个等待键盘事件的函数,参数值delay<=0时等待时间无限长,delay为正整数n时至少等待n毫秒的时间才结束。在等待的期间按下任意按键时函数结束,返回按键的键值(ascii码),等待时间结束仍未按下按键则返回-1。该函数用在处理HighGUI窗口程序,最常见的便是与显示图像窗口imshow函数搭配使用
if (waitKey(delay_time) >= 0)
break;
(3)随机数生成cv::RNG
RNG rng
- 生成高斯随机数
rng.gaussian(double sigma)
- 生成正态分布随机数
rng.uniform(int a, int b)
a和b是取值范围
随机生成line&“OpenCV”
void randomgenerator()
{
Size size(500, 500);
Mat src = Mat::zeros(size, IMREAD_REDUCED_GRAYSCALE_2);
RNG rng(1024);
Point p1, p2;
for (i = 0; i < 100000; i++)
{
p1 = Point(rng.uniform(0, src.cols), rng.uniform(0, src.rows));
p2 = Point(rng.uniform(0, src.cols), rng.uniform(0, src.rows));
p3 = Point(rng.uniform(0, src.cols), rng.uniform(0, src.rows));
Scalar color(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
line(src, p1, p2, color, rng.uniform(0.5, 3), 8);
putText(src, "OpenCV", p3, rng.uniform(0, 7), rng.uniform(0.7, 2.3), color, rng.uniform(2, 3));
imshow("demo", src);
if (waitKey(30) > 0)
break;
}
}
9 模糊图像
1 模糊原理 2 中值滤波 3 双边滤波
(1)模糊原理
- Smooth/Blur是图像处理中最简单和常用的操作之一
- 使用该操作的原因之一就为了给图像预处理时降低噪声
- 使用Smooth/Blur操作其背后是数学的卷积计算
- 通常这些卷积算子计算都是线性操作,所以又叫线性滤波
归一化盒子滤波(均值滤波)
blur(Mat src, Mat dst, Size(xradius, yradius), Point(-1, -1));
注释: Point(-1, -1)意思是将输出值赋给中心点。
高斯滤波
kernel的值分布是二维的高斯分布函数(保留原有的特征) API:
GaussianBlur(Mat src, Mat dst, Size ksize, double sigmaX, double sigmaY = 0, int borderType = BORDER_DEFAULT);
其中sigmax和sigmay必须是正数而且是奇数。
中值滤波:清除椒盐噪声
- 统计排序滤波器
- 中值对椒盐噪声有很好的抑制作用
- 即取该卷积核内所有值的中位数赋给中心像素
API:
medianBlur(Mat src, Mat dst, int ksize);
双边滤波:保持轮廓
可以这样理解,在高斯滤波的基础上,添加了一种类似阈值的概念,按传统的高斯滤波处理,但相邻像素值超出阈值时不做处理,保留了边缘信息。 双边滤波分为空间临近度计算的权值和像素值相似度计算的权值,在边缘附近(高频信号),离的较远的像素不会太多影响到边缘上的像素,这样就能对边缘附近的像素值予以保存。 是输出图像,f 是输入图像,Ω 是以像素点 x 为中心的邻域窗口,w 为滤波核。 滤波核w 由与欧式距离相关的空域核 ϕ 以及和临近像素值差异相关的值域核 ψ 的点积构成。 两个核都按照高斯分布的形式 从公式可以看出当相邻像素值 f(y) 和 f(x) 差值越大时,越小,ψ 的权重越小,这时就成空域核占主要权重,实际上就是普通的高斯滤波形式。 API:
bilateralFilter(Mat src, Mat dst, int d, double sigmaColor, double sigmaSpace, int borderType = BORDER_DEFAULT);