OpenCV与图像算法笔记

这篇博客详细记录了《OpenCV算法精解:基于Python与C++》的内容,涵盖了图像的数字化、几何变换、对比度增强、图像平滑、阈值分割、形态学处理、边缘检测、几何形状检测、傅里叶变换和频率域滤波等方面,深入解析了OpenCV中的图像处理算法,并提供了Python和C++实现。
摘要由CSDN通过智能技术生成

本博客为《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)平移到原点, 再按原点旋转, 再向相反方向平移第一步相同的量, 即三个平移, 原点旋转, 平移矩阵相乘得到变换矩阵
以上可知由基本变换: 平移, 原点放缩, 原点旋转这三个基本矩阵相乘可以等效得到所有仿射变换矩阵, 且这三个基本矩阵的逆易求得, 基本逆矩阵逆序相乘即可得对应仿射变换的逆矩阵

计算仿射变换:
    1.方程法: 任意仿射变换矩阵含有前两行共6个未知数, 故只需要最少三对已知变换前后坐标的点构造6个方程组即可求得
        函数: getAffineTransform(src, dst) 
        Python和C++接口都是返回的形式
        src, dst为两个三行两列的对应变换前后点坐标, 返回一个两行三列的矩阵, 为齐次仿射变换矩阵的前两行, 返回类型为CV_64F
        Python直接用两个三行两列ndarray输入
        C++有两种形式, 第一为输入两个Point2f数组 Point2f src[] = {三个Point2f点}
                     第二为输入两个三行两列的Mat对象, 类型必须是float(CV_32F)
    2.矩阵法:若是知道仿射变换每一步的过程, 则对于正变换可以直接矩阵相乘(先右后左)得到完整仿射变换矩阵
                                     对于逆变换则如上所述, 正变换的基本逆矩阵逆序相乘即可得对应仿射变换的逆矩阵
    函数: getRotationMatrix2D(center, angle, scale) 可返回以任一点为中心的旋转等比例放缩矩阵(等比例放缩时先旋转还是先放缩结果相同)
        angle单位为角度不是弧度, 返回值同样是一个两行三列的矩阵, 返回类型为CV_64F

4.图像中的仿射变换
    插值: 以上为空间坐标的仿射变换, 若要对图像进行仿射变换, 则应用现有变换矩阵由上述计算出变换后坐标, 再用插值算法得到变换后图像
    变换矩阵给了我们图像函数fI到fO的变换关系, 我们的目标是要填满fO的整个定义域, 但是对于部分fO值映射后就成了空值(fI定义域外, 例如小数), 我们反过来考虑, 由逆矩阵得到从fO到fI的映射关系, 在fO的整个定义域遍历依次找到对应位置的fI值插入, 若是fI没有定义, 则根据特定算法求出估计fI值再插入fO, 这便是一个完整插值算法要做的事情
    反过来考虑的好处是我们按照fO的定义域来找, 对于fI上无定义的映射点自然不会有冗余计算, 同时也覆盖了整个fO定义域
    最近邻插值: 最近的一个值作为(x, y)的估计值
              优点: 简单快速; 缺点: 边缘处锯齿状外观
    双线性插值: 任然考虑四个最近点, 分别固定[y], [y]+1, 进行水平方向插值得到f(x, [y]), f(x, [y]+1]), 再进行竖直方向插值, 求得f(x, y)
              其中f(x, [y]) = a*f([x]+1, [y]) + (1-a)*f([x], [y]), a = |x-[x]|
                 f(x, [y]+1) = a*f([x]+1, [y]+1) + (1-a)*f([x], [y]+1), a = |x-[x]|
                 f(x, y) = b*f(x, [y]) + (1-b)*f(x, [y]+1), b = |y-[y]|
              双线性插值是一种二界函数, 有效处理锯齿状外观, 速度也较快, 是很多函数和工具的默认插值法
              更高阶插值能达到更好效果, 但速度也会降低, 例如三次样条插值, Leagendre中心函数和sin(axs)函数, 高阶函数常用二维离散卷积实现

    函数: warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue ]]]]) 可实现图像的仿射变换
         src: 图像; M: 2行3列的仿射变换矩阵(通常是getAffineTransform, getRotationMatrix2D的结果);  
         dsize: 二元元组(宽, 高); flags: 插值法INTE_NEAREST, INTE_LINEAR(默认)等; boderMode: 填充模式BORDER_CONSTANT等, borderValue: 当boderMode是BORDER_CONSTANT时的填充值
    
    函数: resize(InputArray src, OutputArray dst, Size dsize, double fx=o, double fy=0, int interpolation=INTE_LINEAR)
         fx: 水平方向缩放比例, 默认0; fy: 垂直方向缩放比例, 默认0; interpolation: 插值法, 同上
         注意一个问题, dsize优先, 若dszie设定了大小, fx, fy不再起作用; 若dsize为Size(), fx, fy起作用

    函数: rotate(InputArray src, OutputArray dst, int rotateCode)
         用于顺时针旋转90, 180, 270度
         ROTATE_90_CLOCKWISE, ROTATE_180, ROTATE_90_COUNTERCLOCKWISE
二. 投影变换
仿射变换仅涉及二维空间, 若在三维空间发生了旋转, 则为投影变换, 具体原理图多较长参阅《计算机视觉: 一种现代方法》第一章
投影变换涉及三维, 有距离z, 非齐次形式的投影矩阵也为3x3
函数: getPerspectiveTranstorm(src, dst) 返回CV_64F投影矩阵
     两个重载函数, 一接收两个四个点的Point2f数组, 二接收两个Mat_<float>(4, 2)
函数: warpPerspective(原图, 结果, 3x3投影矩阵, 结果大小) 返回投影图像
函数: 画圆/圆圈circle(InputOutputArray img, Point center, int radius, const Scalar& color, int thickness = 1, int lineType = LINE_8, int shift = 0) 
     画矩形rectangle, 椭圆ellipse, 线段line  
注: 定义鼠标事件: 定义回调函数, 参数列表(int event, int x, int y, int flags, void *param), event为事件, xy为鼠标事件的坐标, flags自定义标志, param为窗口内的图像等, 写好需要的每个event(CV_EVNET_......)对应的操作
                创建窗口, setMouseCallback绑定回调函数, 循环show出来不断刷新, 程序结束后解
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值