本博客为《OpenCV算法精解:基于Python与C++》一书(参阅源代码链接)的阅读笔记,根据理解对书中绝大多数算法做了总结和描述,对Numpy较为熟悉,Python方面仅对与C++不同的注意事项做了标注。书作者整体按照冈萨雷斯的经典教材《数字图像处理(第三版)》和OpenCV知识脉络组织内容,每个算法均用Python和C++两种语言实现。除官方函数外本书给出了多数算法函数的自定义版本便于读者理解,以及部分官方未包含的算法(利于导向滤波、Marr-Hildreth边缘检测等)
源代码下载地址 www.broadview.com.cn/32495
第二章: 图像的数字化
一. 创建一个Mat
1. Mat(行, 列, 类型)
2. Mat(Size(列, 行), 类型)
3. 对已声明Mat对象 m.create 格式同上
4. Mat::ones, Mat::zeros
5. 直接初始化小型矩阵值 (Mat_<int>(2,3) << 1,2,3), 把int和后面元素换成Vec2f等可以直接初始化双通道矩阵
6. Mat(vector<Point2f>)构造N行1列的双通道矩阵Mat
7. 可以用Mat的reshape方法变换矩阵形状和通道
二. Mat的基本成员变量和函数
rows, cols, size(), channels(), total(), dims, 转置t()等
三. 访问Mat对象中的值
1. 成员函数at(最慢,最可读): m.at<float>(r,c), 可用Point对象: m.at<int>(Point(c,r)) 注:Point和Size对象先列再行
2. 成员函数ptr可获取任意一行的行指针: const int * ptr = m.ptr<int>(r) 获取行指针后访问列 ptr[c]
3. 成员函数isContinuous可获取矩阵每一行之间存储是否连续, 如果连续,则可:
int * ptr = m.ptr<int>(0) //获取矩阵首地址
ptr[r*m.rows+c] 访问(r,c)
4. 成员函数step和data
step[0]获取矩阵每一行所占字节数, step[1]获取每一数值所占字节数, data为指向第一个数值的指针, 类型为uchar
*((int*) (m.data + r*m.step[0] + c*m.step[1])) //访问int类型矩阵的(r,c)
注: 矩阵的同一行和一维数组一样存储必连续, 不同行可能连续可能不连续,对于连续的可用方法三访问
step[0]不仅可以得出连续矩阵每一行所占字节数, 若行间隔, 间隔字节数也被计入了step[0]内(若有间隔则间隔也是固定的)
四. 列向量Vec
创建: Vec<Typename _Tp, int _cn>
成员变量类似Mat, 有cols, rols等
用(), [] 访问
官方别名 typedef Vec<uchar, 3> Vec3b,
typedef Vec<int, 2> Vec2i,
typedef Vec<float, 4> Vec4f,
typedef Vec<double, 3> Vec3d 等等
单通道Mat每一元素为一个数值, 多通道矩阵每一元素为一个Vec
五. 多通道矩阵
1. 元素为Vec, 同样连续存储
2. 访问方式同单通道的4种方式, 例如三通道对应的typename变为Vec3f, Vec3b等等
例如第四种访问方式:
Vec3f* ptr = (Vec3f*)(mm.data + r*mm.step[0] + c*mm.step[1]);
之后取ptr值: *ptr
3. 通道分离
使用函数split(mat, vector<Mat>)
先创建一个元素为Mat的vector, 调用split函数把原多通道Mat分离为多个单通道Mat存到vector中
4. 通道合并
函数原型一: void merge(const Mat * mv, size_t count, OutputArray dst)
多个单通道矩阵初始化一个矩阵 Mat plane[] = {plane0, plane1, plane2}, 声明一个目标矩阵mat
merge(plane, 3, mat)
函数原型二: void merge(InputArrayOfArrays mv, OutputArray dst)
将单通道矩阵放入mat的vector中, merge(plane, mat)
六. 获取矩阵某一区域的值
1. 某一行: m.row(r), 某一列: m.col(c)
2. 连续行: m.rolRange(start, end), m.rolRange(Range(start, end))
连续列: m.colRange(start, end), m.colRange(Range(start, end)) 都是左闭右开区间
3. clone和copyTo
clone: 2的方法仍然指向原矩阵, 末尾加.clone()创建并指向副本
copyTo同理, matrix.rolRange(start, end).copyTo(r_range)
4. Rect类
Rect(int_x, int_y, int_width, int_height);
Rect(int_x, int_y, Size(int_width, int_height)), Rect(Point2i&pt1, Size(int_width, int_height)) //即坐标可以int x,y也可以Point, 宽高可以int width,height也可以Size
Rect(Point2i&pt1, Point2i&pt2) //左上角右下角 注:实测不包括右下角,为左上角到右下角左上一个点的矩形roi,即也是左闭右开
七. 矩阵运算
1. 加法: 法一: OpenCV重载了+号, 适用两种情况, 第一是矩阵+矩阵, 要求同size同类型; 第二是矩阵加数值, 不要求同类型, 会把数值类型转换后加到矩阵每一个元素上
注: 不会发生溢出情况, 会截断为类型最大值
Python则不同, 缺点ndarray的加法不会截断, 而是溢出, 这点与C++接口区别
优点ndarray有强大的广播能力, 也适用于不同类型相加(取范围大的类型)
同时cv2.add函数接口同大部分OpenCV的Python函数一样, 不再在参数中有dst, 而是将dst返回
法二: add(InputArray src1, InputArray src2, OutputArray dst, InputArray mask=noArray(), int dtype=-1)
使用add而不是+时候矩阵类型可以不同, 输出类型可以由dtype参数自行指定, 默认参数-1仅适用于同类型时
2. 减法: 与加法同理, 用-或者subtract函数, 注意事项相同, C++截断Python溢出
3. 点乘: 法一: Mat成员函数mul, dst = src1.mul(src2)
法二: multiply(InputArray src1, InputArray src2, OutputArray dst, double scale=1, int dtype=-1)
scale为点乘结果基础上乘的系数, dtype同上指定类型, -1同类型
注: Python的ndarray重载了*表示点乘, 用法同+, -, C++的OpenCV也重载了*但是表示矩阵乘法
4. 点除: OpenCV重载了/, 同时有函数divide, 用法同上
注:Python的ndarray在uint8类型时除以0得0, 其他情况返回inf
5. 矩阵乘法
法一: 重载了*, 要求符合矩阵乘法要求, 类型一致, *仅支持同为float或double, 两个双通道矩阵也可相乘, 两通道会被分别当成实部和虚部
法二: gemm(InputArray src1, InputArray src2, double alpha, InputArray src3, double beta, OutputArray dst, int flags=0)
alpha为相乘后的系数, beta为src3系数, flags控制了三个矩阵是否转置
flags=0: alpha*src1*src2 + beta*src3
flags=GEMM_1_T: alpha*src1的转置*src2 + beta*src3
flags=GEMM_2_T: alpha*src1*src2的转置 + beta*src3
flags=GEMM_3_T: alpha*src1*src2 + beta*src3的转置
不需要src3时候可以用NULL, beta设为0, 即*等价于gemm(src1, src2, 1, NULL, 0, dst, 0)
Python: np.dot(src1, src2)结果返回
6. 其他运算:
指数函数exp, 对数函数log(以e为底)函数, 仅支持float和double
Python的ndarray的exp和log支持任意数值类型,返回各种长度的float
幂指数函数pow(Python为power), 开平方运算sqrt
注意截断的问题
八. 图像灰度化
1. 图像读取imread函数, 第一个参数为文件路径字符串, 第二个参数flags:
0: IMREAD_GRAYSCALE 灰度图(若是读取的为彩色图会自动灰度化), 1: IMREAD_COLOR BGR图, 默认1, 更多可选参数参考imgcodecs.hpp
2. 显示imshow函数, 第一个参数字符串的窗口名, 第二个要显示的Mat对象
namedWindow(窗口名, 模式) 可预先创建窗口, imshow同时创建的窗口为不可调的
模式0: WINDOW_NORMAL大小可调
1: WINDOW_AUTOSIZE(默认)按图像大小, 不可调
imshow图像需要配上waitKey(等待毫秒数)才能被观察, 最少1, 0为等待任意键按下
destroyAllWindows()关闭所有窗口
OpenCV的彩色转灰度公式 gray = 0.114B + 0.587G + 0.299R
第三章 几何变换
一.仿射变换
仿射变换 = 线性变换 + 平移
非齐次形式 x' = Ax + b
齐次形式 x' = Ax A(3x3矩阵)左上角为一个二维的线性变换, 最右列上两个元素为平移量, 最下行为(0, 0, 1)
基本仿射变换包括: 平移, 缩放, 旋转
1.平移: 单位阵基础上带两个平移量, 方向同正负
2.放缩: 以原点为中心缩放, 平移量为0, 主对角前两个量分别表x, y轴的放缩, 绝对值大于1放大, 小于1缩小
以任一点(x0, y0)为中心缩放相当于先将(x0, y0)平移到原点, 再按原点缩放, 再向相反方向平移第一步相同的量, 即三个平移, 原点放缩, 平移矩阵相乘得到变换矩阵
公式(x', y') = (x0 + Sx(x-x0), y0 + Sy(y-y0))
3.旋转: 以原点为中心的二维齐次旋转阵仅有左上角四个量, 右下角为1, 其余为0; 基本旋转阵是对称阵, 其逆矩阵就是他的转置
同放缩变换, 以任一点(x0, y0)为中心旋转相当于先将(x0, y0)平移到原点, 再按原点旋转, 再向相反方向平移第一步相同的量, 即三个平移, 原点旋转, 平移矩阵相乘得到变换矩阵
以上可知由基本变换: