# OpenCV+QT
# 文件项目配置
属性(相对路径)
配置调试选择 x64
常规 —— 输出目录 —— ..\..\bin
调试 —— 工作目录 —— ..\..\bin
C/C++—— 附加包含目录 —— ..\include
链接器 —— 常规 —— 附加库目录 —— ..\..\lib
链接器 —— 输入 —— 附加依赖项 —— 在前面加 opencv_world331d.dll
# 开发遇到的坑
# ui 文件打不开
需要手动右键 ui文件
设置打开方式,选择 qtdesigner.exe
,并设置为默认打开方式
# 源文件打不开
要考虑是否创建项目的时候漏了一些库没有选择,可以参照 pro 文件和 cmakelist 文件的配置看少配置了什么
# mat
Mat
是 OpenCV 中的基本图像容器。
1 | //地址遍历不一定连续的Mat |
mat.data
是指向图像数据的指针,类型是uchar*
(无符号字符指针),表示图像的原始像素数据。
mat.step
是每行像素数据在内存中的字节数(步长),它可以比mat.cols * es
大,特别是在行有对齐要求时。
es
是每个像素的字节数(对于CV_8UC3
类型,es
是 3,因为每个像素有 3 个通道:B、G 和 R)。
row * mat.step
计算出当前行在图像数据中的起始位置。
col * es
计算出当前列在当前行中的偏移量。
&mat.data[row * mat.step]
得到当前行的起始地址,再加上col * es
得到当前像素的起始地址。
# ROI 感兴趣区域裁剪图像
# cv::Rect rect(100,100,300,300)
100: 横向位置 100
100:从上往下 100
然后在这个点取一个矩形 300*300
1 | int main(int argc, char* argv[]) |
# 像素格式和灰度图
# RGB
显卡输出的数据是 RGB 形式的,三个字节一个像素
# YUV
亮度,色度,饱和度,电视,黑白电视,Y 信号就行
存储空间会比 RGB 小,两个字节一个像素
压缩算法基于 YUV,更利于做压缩算法,都是转化为 YUV 然后用压缩算法
图像处理,最终显示的时候要把 YUV 转换为 RGB
# RGAY
灰度图,一个字节 0~25
一个字节一个像素 ,高速摄影
# cvtColor(src,img,COLOR_BGR2GRAY)
#include<opencv2/imgproc.hpp>
格式转换,BGR 转换为灰度图
# 手动实现转换灰度图
1 | //手动实现转换灰度图 |
# 二值化和阈值
# 二值化
图片的一种存储方式
一个黑一个白
有大概五种算法
# 1、 THRESH_BINARY
二进制阈值化
# 2、 THRESH_BINARY_INV
反二进制阈值化
根据参数,如果满足条件就变成白色,如果不满足就变成黑色,反向处理
1 | //二进制阈值化 |
# 改变图片的对比度和亮度
# g(i,j) = a*f(i,j) + b
目标像素 = a (对比度) * 原始颜色 + b (亮度)
a 1.0~3.0 (对比)
b 0~100 (亮度)
1 | void ChangeGain(Mat& src, Mat& des, float a, int b) { |
# saturate_cast<uchar>
防止溢出
可能会溢出,超过 255,就不是全白,反射,变成了全黑,不是我们想要的,所以要防止溢出,设置超过 255 它就是 255,全白,R 这个通道超过 255 就是全红,小于 0 就设为 0
# opencv 函数 convertTo()
1 | //main() |
# 图像尺寸调整
# INTER_NEAREST
邻近算法
小变大 —— 拷贝周围的像素,会生成马赛克
# 自定义
1 | int sx,sy = 0;//原图对应的坐标 |
1 | //自定义缩放代码 |
1 | //main() |
# OpenCV 的 resize()
1 | //opencv自带的函数,有多线程处理、优化 |
第 4、5 个参数表示 fx,fy, 当 Size 为空时,它们作为一个比例来乘以原图的大小
第 6 个参数,算法类型,默认是双线性插值,这里是邻近算法
# CV_INTER_LINEAR
双线性插值(缺省使用)
# 滤波
解决放大图像边界会出现马赛克、模糊的情况
# 双线性内插值
使放大的图像边界更加平滑
使用:直接把方法参数处改为 INTER_LINEAR
# 图像金字塔
在图像放大缩小、拼接、扭曲都可以用到这种算法
1 | //高斯金字塔,原图,目标图 |
# 高斯金字塔(向下采样缩小)
用来向下采样
把整个分辨率降低
比如图像是 8*8 的,卷积后将所有偶数行和列去除,就缩小成了 4*4 的图像
G (i+1) 表示上一层
# 高斯内核
提供好的固定的矩阵
opencv 提供的高斯金字塔只支持这种 5*5 的矩阵
# 拉普拉斯金字塔
用来从金字塔低层图像重建上层未采样图像
# 两幅图像混合(blending)
# 公式
dst = src1*a + src2*(1-a) + gamma
最终叠化成果的像素集 = 原图 1 * 透明度 + 原图 2 * 透明度
(1-透明度)
是为了融合度高,一个高一个低
# OpenCV- addWeighted()
第五个参数(0.0):对图像的增益,比如颜色更深,白色更亮
第六个参数(dst):最终生成的目标
# 图像旋转和镜像
1 | //旋转rotate |
# 旋转
# 镜像
0 (x)—— 上下做镜像
1 (y)—— 左右做镜像
-1—— 两个都做
# 通过 ROI 图像合并
1 | //roi图像合并 |
# ffmpeg 工具抽取剪切音频合并视频
# 抽取音频
ffmpeg.exe -i 1.avi -vn 1.mp3
-i 表示源 1.avi 输入文件 -vn 表示不转换视频 1.MP3 输出文件
# 剪切音频
ffmpeg -ss 0:0:30 -t 0:0:20 -i input.mp3 -c copy output.mp3
-ss 表示开始时间 -t 表示剪切时间
# 音视频合并
ffmpeg.exe -i 1.mp3 -i 1.mp4 -c copy out.mp4
# OpenCV VideoCapture 读取视频
# VideoCapture 类
这是一个读取视频的类,视频源可以是文件、摄像头、RTSP 流都可以
# 打开摄像头方式
# bool open(int index)
这个参数 index 索引对应你的所有摄像机列表
# open(int cameraNum.int apiPreference)
可以手动选择 api 接口,默认是 0 ,自动监测
# VideoCapture cap(index)
这是一个构造函数
# 打开视频流文件
# bool open(const String &filename)
# VideoCapture cap(const String& file)
# bool open(const String &filename,int apiPreference)
# 关闭和空间释放
# ~VideoCapture()
析构函数,智能指针,在复制之后,引用计数会加一,直到没有引用,才会释放,通过智能指针,空间的释放不用做太多的管理
如果不想要转码的时候调用全部 gpu,可以修改源码
# release()
主动释放,如果 VideoCapture 被两次调用,一个引用 - 1,另一个空间也不会被释放掉,只是把你这次的引用 - 1
# 读取一帧视频
1 | VideoCapture video; |
# read(OutputArray image)
它先是解压缩(解码),然后对图片做了色彩转换
h264 用一帧画面存储整个,也是用 jpg 来压缩,后面 50 帧画面只存储与这一帧的变化,这样的压缩率非常高,这样的话就存在一个问题,每一帧必须都要解码,比如说第十帧是针对第九帧的变化,那么取出一整个画面就是不对的,所以我们可以先把前面的解码,先不做图像转换,不显示,以提高效率
# bool grab()
读取并解码
# virtual bool retrieve(OutputArray image,int flag = 0 )
图像色彩转换
# vc>>mat
# 获取视频、相机属性
其他属性的获取可以看 OpenCV 的 api 文档 ——OpenCV Flags for video I/O
1 | int fps = video.get(CAP_PROP_FPS);//帧率,使用get(),里面放以下的参数 |
# CAP_PROP_FPS
帧率
一秒钟的帧数
# CAP_PROP_FRAME_COUNT
总帧数
计算视频时长:总帧数除以帧率(s)
# CAP_PROP_POS_FRAMES
播放帧的位置
跳帧,下一帧
# CAP_PROP_FRAME_WIDTH HEIGHT
可以获取到视频帧的宽度高度,虽然一解码的时候可以获取到这个信息,但是有些处理,还没有读帧的时候需要用到这个高度,就需要用到,比如窗口自适应
# 设置视频播放的进度
1 | int cur = video.get(CAP_PROP_POS_FRAMES); |
# CAP_PROP_POS_MSEC
毫秒位置
毫秒 -> 帧 ->ffmpeg 时间,要转两次,浮点数值来回转换,会出现数据的丢失,所以建议使用帧位置
# CAP_PROP_POS_FRAMES
帧位置
# VideoWriter
要主动释放 release ()
# void write(const Mat&)
# CvVideoWriter_FFMPEG::writeFrame
# 录制摄像头视频并存储代码
1 | int main(int argc, char* argv[]) |
# 视频编辑器
# bug 修复
1、信号没有发送
线程没有开启,要在线程中加入 start()
2、在头文件中的函数要在 cpp 中有
# 流程
# 前期
- 设置页面背景色
- 取消顶部菜单栏
setWindowFlags(Qt::FramelessWindowHint);
- 添加关闭按钮,设置信号与槽
# 视频显示
- ui 添加 glWidget,并提升为新建立的类
- 在主.cpp 中添加上传文件按钮,并设置 open 函数,连接信号槽
1 | void VideoUI::open() |
- 创建线程,开启线程,在线程中打开文件,在 VideoUI 的 open 函数中调用 videoThread 类中的 get 创建对象,执行 open 函数
1 | //用于打开视频文件或摄像头,并从中读取视频帧 |
- 线程启动后会自动调用 run (), 读取每帧视频存到 mat 对象中
- 传递信号至 ui 中的显示容器中,用 setImage1 () 处理图像,paintEvent () 刷新图像
# 视频进度条
# 设置进度条随视频变化:
在 VideoUI 中设置定时器,定时器获取到当前视频的播放状态然后再改变滑动条的位置
videoThread 返回播放进度或者返回成员也能算出进度
# 拖动进度条变化视频:
- 拖动进度条发送信号,带有参数,传进槽函数中处理
- 在槽函数中将参数传入线程中调用 seek 函数,seek 函数获取当前视频的总帧数,计算出当前的位置
- 传到另一个 seek 中设置画面到算出的这个位置
# 视频亮度和对比度调整
videoUI->Filter->imagePro
# 添加 videoFilter 类
相当于一个任务列表,执行多个任务,把任务依次给 imagePro 来执行,所以要先搞 imagePro
# 接收界面设置的参数
# 输出处理后的视频
# 后续每添加一个功能
先做函数,再加过滤器(任务),调用对应处理的函数,然后就是 ui 部分
# videoThread.h
1 |
|
# videoThread.hpp
# mutex 互斥锁
用来防止多个线程同时访问 cap1
对象,避免数据竞争,在执行 “任务” 的时候上锁,防止别的线程插队,等执行完后再解锁,这个上锁解锁的原则就是晚锁早解,提高效率
1 |
|
# videoWidget.hpp
# void videoWidget::paintEvent(QPaintEvent* e)
这是一个覆盖(override)了 QOpenGLWidget
中的 paintEvent
函数。 paintEvent
在 Qt 中是一个虚函数,当窗口部件需要重绘时(例如窗口大小改变,或者通过 update()
函数请求重绘时),Qt 会自动调用这个函数。
1 |
|