OpenCV4 快速入门 学习笔记分享【第八章】
本人正在学习 OpenCV 计算机视觉相关知识,此篇为个人学习笔记,参考书籍为冯振等人的《OpenCV4 快速入门》,主要记录 OpenCV 在 Linux 环境下的编译、运行实操流程与相关知识点总结。
笔记内容均为亲自实操验证,过程中踩过不少编译配置、运行参数的坑,整理出来一方面用于自己复盘巩固,另一方面也希望能帮助到刚入门 OpenCV、在编译运行示例程序时遇到问题的小伙伴。文中内容如有疏漏或错误之处,欢迎各位大佬评论区指正交流,共同进步~
第八章 图像分析与修复
8.1 傅里叶变换
8.1.1 离散傅里叶变换 dft()函数
定义:傅里叶变换在时域和频域上都呈现离散的形式,将时域信号的采样变成离散时间傅里叶变换频域的采样。结果是复数,分成实数图像+虚数图像 或者 幅值图像+相位图像
图像中像素波动较大的是高频区域,体现细节和纹理信息。低频体现轮廓
void cv::dft(InputArray src, // 输入数组(单通道/双通道,浮点型)
OutputArray dst, // 输出结果(尺寸与输入一致)
int flags = 0, // 变换标志位(核心参数)
int nonzeroRows = 0 // 优化参数,默认0即可
);
|
标志位flags |
数值 |
描述 |
|
DFT_COMPLEX_OUTPUT |
16 |
强制输出复数格式(双通道),正向变换常用 |
|
DFT_REAL_OUTPUT |
32 |
输入为实数、输出为实数,仅用于逆变换 |
|
DFT_INVERSE |
1 |
执行逆傅里叶变换(频域→时域),不设置则默认正向变换 |
|
DFT_SCALE |
2 |
逆变换时归一化缩放(匹配原始图像幅值,必加) |
|
DFT_ROWS |
4 |
每一行进行正或逆变换,可以处理三维或更高的离散变换,减少资源利用 |
|
DFT_COMPLEX_INPUT |
64 |
输入矩阵必须具有两个通道,函数默认输入数据是复数 |
注意:执行情况见书p255
- 离散傅里叶逆变换 idft()函数 (原型同上)
- 计算最优的输入矩阵尺寸 getOptimalDFTSize()函数
最优尺寸是2、3、5的公倍数
int cv::getOptimalDFTSize(int vecsize) //vecsize表示矩阵的最佳行数或列数
- 不对图像进行缩放,在图像周围形成外框 copyMakeBorder()函数
void cv::copyMakeBorder(
InputArray src, // 输入:原始图像/矩阵
OutputArray dst, // 输出:添加边框后的目标矩阵,数据类型与src完全一致
int top, // 输入:图像**顶部**填充的边框像素个数,非负整数
int bottom, // 输入:图像**底部**填充的边框像素个数,非负整数
int left, // 输入:图像**左侧**填充的边框像素个数,非负整数
int right, // 输入:图像**右侧**填充的边框像素个数,非负整数
int borderType, // 输入:边框填充模式枚举值,决定边框像素的生成规则
const Scalar& value = Scalar() // 输入:可选参数,仅borderType=BORDER_CONSTANT时生效,指定填充的固定像素值
);
- 计算由两个矩阵组成的二维向量矩阵的幅值矩阵 magnitude()函数
void cv::magnitude(x,y,输出的幅值矩阵)
示例代码:
代码分为两大块:矩阵 DFT 正逆变换验证 + 图像 DFT 变换与频谱可视化。
8.1.2 傅里叶变换进行卷积
可以将两个矩阵的卷积转换成两个矩阵的乘积,提高卷积速度
- 计算两个复数矩阵的乘积 mulSpectrums()函数
void cv::mulSpectrums(
InputArray a, // 输入第一个复数频谱矩阵(双通道CV_32F/CV_64F)
InputArray b, // 输入第二个复数频谱矩阵(尺寸、类型必须与a完全一致)
OutputArray c, // 输出乘法结果矩阵(尺寸、类型与输入一致)
int flags, // 运算标志位,核心控制共轭运算
bool conjB = false // 可选参数,是否对矩阵b先取共轭再相乘(false不进行共轭)
);
8.1.3 离散余弦变换 dct()函数
类似离散傅里叶变换,但只使用实数,用于对信号和图像有损数据压缩中
只支持偶数大小的数组,最佳尺寸通过2*getOptimalDFTSize((N+1)/2)
void cv::dct(输入矩阵(必须是浮点数),输出矩阵,
转换方法的标志(DCT_INVERSE对一维或二维进行逆变换,,,DCT_ROWS转换多个向量))
- 离散余弦变换的逆变换idft()函数 (原型同上)
8.2 积分图像
快速计算图像某些区域像素的平均灰度
积分图像比原图像大一圈的图像,原图像是n*n,则积分图像是(n+1)*(n+1)
积分图像像素值=该像素点与原点组成矩形区域内所有像素值的和
分类:标准求和积分图像、平方和积分图像、倾斜求和积分图像
- 标准求和 integral()函数
void cv::integral(输入图像,输出图像,输出图像数据类型(默认-1))
- 平方求和 integral()函数
void cv::integral(输入图像,输出标准求和图像,输出平方求和图像,
输出标准求和数据类型(默认-1),输出平方求和图像标志(默认-1))
- 倾斜求和 integral()函数
void cv::integral(输入图像,输出标准求和图像,输出平方求和图像,输出倾斜求和图像,
输出标准求和+倾斜求和数据类型(默认-1),输出平方求和图像标志(默认-1))
8.3 图像分割
将图像中属于某一类的像素点与其他像素点分开
8.3.1 漫水填充法 floodFill()函数
根据像素灰度值之间的差值寻找相同区域以实现分割
种子点按照一定规则向外扩散,形成具有相似特征的独立区域,进而实现图像分割
步骤:1. 选择种子点 2.以种子点为中心,判断4-邻域或8-邻域的像素值与种子点像素值的差值,将差值小于阈值的像素点添加进区域内 3.将新加入的像素点作为新的种子点,反复执行
int floodFill(
InputOutputArray image, // 输入/输出图像:支持CV_8U/CV_32F,单通道灰度/三通道彩色
InputOutputArray mask, // (可选)掩码图像:单通道CV_8U,尺寸比原图大2像素
Point seedPoint, // 种子点:填充起始坐标 (x,y),x=列,y=行
Scalar newVal, // 填充的目标颜色/灰度值,彩色图为Scalar(B,G,R)
Rect* rect = nullptr, // 可选输出:返回填充区域的最小外接矩形
Scalar loDiff = Scalar(), // 向下差值阈值:允许低于种子点的最大差值
Scalar upDiff = Scalar(), // 向上差值阈值:允许高于种子点的最大差值
int flags = 4 // 填充模式:4邻域/8邻域,默认4邻域
);
8.3.2 分水岭法 watershed()函数
从全局出发,对全局进行分割
过程:1. 排序过程,对图像灰度级进行排序,确定灰度值较小的像素点 2.淹没过程,对每个最低点开始注水,不断淹没周围像素点,不同的注水出汇集在一起,形成分割线
void cv::watershed(输入图像,输出的标记结果)
8.3.3 Grabcut法
使用高斯混合模型估计目标区域的背景和前景,并且会产生众多较小的区域
通过迭代的方法解决能量函数最小化的问题,使更具有可靠性
void cv::grabCut(
InputArray img, // 输入图像,必须是8位三通道彩色图(BGR)
InputOutputArray mask, // 掩码矩阵(CV_8U单通道),标记像素类型
Rect rect, // 包含前景的矩形框(交互式框选区域)
InputOutputArray bgdModel, // 算法内部使用的背景模型(无需手动修改)
InputOutputArray fgdModel, // 算法内部使用的前景模型(无需手动修改)
int iterCount, // 迭代次数,推荐5~10次,次数越多精度越高
int mode = GC_EVAL // 操作模式(分割模式标志)
);
|
宏定义MASK |
数值 |
含义 |
|
GC_BGD |
0 |
确定背景(用户手动标记的绝对背景) |
|
GC_FGD |
1 |
确定前景(用户手动标记的绝对前景) |
|
GC_PR_BGD |
2 |
可能背景(算法自动判定) |
|
GC_PR_FGD |
3 |
可能前景(算法自动判定) |
修改为摄像头读取图像示例:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
VideoCapture cap(0);
// 判断摄像头是否成功打开
if (!cap.isOpened())
{
cout << "摄像头打开失败!" << endl;
system("pause");
return -1;
}
// 2. 读取一帧摄像头图像(GrabCut适合单帧处理,这里取首帧做分割演示)
Mat img;
cap >> img;
if (img.empty())
{
cout << "未获取到摄像头帧!" << endl;
cap.release();
return -1;
}
//绘制矩形
Mat imgRect;
img.copyTo(imgRect); //备份图像,方式绘制矩形框对结果产生影响
Rect rect(80, 30, 340, 390);
rectangle(imgRect, rect, Scalar(0, 0, 255),2);
imshow("选择的矩形区域", imgRect);
//进行分割
Mat bgdmod = Mat::zeros(1, 65, CV_64FC1);
Mat fgdmod = Mat::zeros(1, 65, CV_64FC1);
Mat mask = Mat::zeros(img.size(), CV_8UC1);
grabCut(img, mask, rect, bgdmod, fgdmod, 5, GC_INIT_WITH_RECT);
//将分割出的前景绘制回来
Mat result;
for (int row = 0; row < mask.rows; row++)
{
for (int col = 0; col < mask.cols; col++)
{
int n = mask.at<uchar>(row, col);
//将明显是前景和可能是前景的区域都保留
if (n == 1 || n == 3)
{
mask.at<uchar>(row, col) = 255;
}
//将明显是背景和可能是背景的区域都删除
else
{
mask.at<uchar>(row, col) = 0;
}
}
}
bitwise_and(img, img, result, mask);
imshow("分割结果", result);
waitKey(0);
// 8. 释放资源
cap.release();
destroyAllWindows();
return 0;
}
8.3.4 Mean-Shift法 pyrMeanShiftFiltering()函数
均值漂移法,一种基于颜色空间分布的图像分割法,经过滤色的分色图像,颜色会渐变,细纹纹理会平缓
每个像素点用五维向量(x,y,b,g,r)表示,滑动窗口由半径和颜色幅度构成,半径决定了滑动窗口的范围,颜色幅度决定了半径内像素点分类的标准。
void pyrMeanShiftFiltering(
InputArray src, // 输入图像:8位三通道彩色图(BGR格式,灰度图需转三通道)
OutputArray dst, // 输出图像:尺寸、类型与原图一致
double sp, // 滑动窗口半径(Spatial Window)
double sr, // 色彩窗口幅度(Color Window)
int maxLevel = 1, // 金字塔最大层数,默认1层
TermCriteria termcrit = TermCriteria( //算法迭代停止的条件
TermCriteria::MAX_ITER + TermCriteria::EPS,
5, // 最大迭代次数
1) // 精度阈值
);
- 算法迭代停止的条件 TermCriteria()结构体
cv::TermCriteria(
int type, // 终止条件类型,支持组合取值
int maxCount, // 最大迭代次数或者元素数
double epsilon // 精度阈值(变化量小于该值则停止)
);
|
枚举常量 type |
数值 |
含义 |
|
TermCriteria::MAX_ITER |
1 |
以最大迭代次数作为终止条件 |
|
TermCriteria::EPS |
2 |
以精度收敛阈值作为终止条件 |
|
TermCriteria::MAX_ITER + TermCriteria::EPS |
3 |
组合条件,满足任一即停止(最常用) |
8.4 图像修复
对水印或污染的图像进行修复 inpaint()函数
void inpaint(
InputArray src, // 输入图像:8位单通道灰度图 / 8位三通道彩色图
InputArray inpaintMask,// 修复掩码:单通道8位图,非0像素表示需要修复的区域,0像素为正常区域
OutputArray dst, // 输出修复后的图像
double inpaintRadius, // 修复邻域半径:算法参考的周边像素范围
int flags // 修复算法选择:INPAINT_NS基于Navier-Stokes算法修复图像(0);
//INPAINT_TELEA基于Alexandru Telea算法图像
);
8.5 总结

至此,第八章就结束了,望各位读者“一键三连”,再次感谢读到这里的各位读者。有任何问题欢迎评论区留言或者私信。
更多推荐
所有评论(0)