一、引言
最近在学习图像处理的《直方图处理》,对直方图均衡处理效果感觉非常有用。
以前学习Moviepy音视频剪辑时,用的卓别林的一个黑白视频片段,感觉视频的噪点比较多,画面也整体偏暗,不禁想看看如果对其进行直方图均衡会怎么样。如是开干,但进展并不很顺利,下面就来谈谈遇到的问题。
二、基础知识介绍
1、Moviepy的视频变换fl_image方法
由于是对视频图像进行灰度处理,因此需要逐帧读取视频帧,每帧应用直方图均衡。该视频变换处理有多种方法实现,老猿使用fl_image。
fl_image方法是对get_frame方法获取的帧进行变换的方法,本质上是Clip的fl方法在内容变换方面的一种变种和应用。
调用语法:fl_image(self, image_func, apply_to=None)
参数说明:
- image_func:参数image_func是对剪辑帧进行图像变换的函数,带一个参数,参数就是要处理的帧,这个帧直接通过get_frame去获取,image_func函数的返回值为经过变换后的帧
- apply_to:apply_to表示变换是否需要同时作用于剪辑的音频和遮罩,其值可以为’mask’、‘audio’、[‘mask’,‘audio’]
对比fl调用方法fl(self, fun, apply_to=None, keep_duration=True):
-
fl_image由于只变换内容,因此不涉及时间的变换,keep_duration就是默认为True
image_func不带时间参数,这是因为系统默认调用get_frame(t)来获取帧,无需image_func带时间参数 -
fl_image本质上是执行如下语句来完成帧内容的变换:fl(lambda gf, t: image_func(gf(t)),
apply_to)
更多关于Moviepy视频fl_image变换处理的介绍请参考《moviepy音视频剪辑:视频剪辑基类VideoClip的属性及方法详解》。
2、Moviepy彩色视频转黑白视频变换blackwhite函数
blackwhite函数用于将剪辑变成灰度剪辑,也就是将剪辑中的彩色像素灰度化。
调用语法:blackwhite(clip, RGB = None, preserve_luminosity=True)
参数说明:
- clip:要处理的剪辑,通过fx或subfx调用时,会将调用者的实例对象self传入
- RGB:浮点数三元组,用于设置RGB三种颜色的权重,缺省值为None,如果为None,则为1:1:1,如果设置为‘CRT_phosphor’,则RGB = [0.2125, 0.7154, 0.0721]
- preserve_luminosity:preserve_luminosity用于控制是否保持亮度,如果保持亮度不变,则最终的RGB三个值相加为1。在这里的亮度luminosity不是lightness,实际上是对明度的度量,也称为灰阶值,是不同权重的R、G、B的组合值。实际上亮度是对颜色的明度(brightness)的一种度量
3、OpenCV的直方图均衡处理equalizeHist函数
OpenCV的直方图均衡处理非常简单,就是对需要处理的黑白图像执行cv2.equalizeHist函数,返回图像即为直方图均衡后的图像。
4、OpenCV的图像色系转换cvtColor
cv2.cvtColor是openCV提供的颜色空间转换函数。
调用语法:cvtColor(src, code, dstCn=None)
其中:
- src:要转换的图像
- code:转换代码,表示从何种类型的图像转换为何种类型,如cv2.COLOR_BGR2GRAY就是将BGR格式彩色图像转换成灰度图片,具体转换代码请参考官网文档
- dstCn:目标图像的通道数,如果为0表示根据源图像通道数以及转换代码自动确认
三、实现思路及处理视频内容
总体处理非常简单,就是加载视频剪辑后,对剪辑进行fl_image变换,变换参数中的处理函数就传递直方图均衡处理相关的函数。
原始视频是卓别林的一个黑白视频片段,下面是该段视频的2个截图:
四、实现过程及遇到的问题
1、直接在fl_image调用直方图均衡的equalizeHist函数
实现代码如下:
from moviepy.editor import *
import cv2def test():clipV = VideoFileClip(r"F:\video\zbl1.mp4")newclip = clipV.fl_image(cv2.equalizeHist, apply_to=['mask'])newclip.write_videofile(r"F:\video\temp\zbl1.mp4")test()
结果执行报错(很长的报错信息,仅截取关键部分):
File "C:\Program Files\Python38\lib\site-packages\moviepy\video\VideoClip.py", line 490, in <lambda>return self.fl(lambda gf, t: image_func(gf(t)), apply_to)
cv2.error: OpenCV(4.3.0) C:\projects\opencv-python\opencv\modules\imgproc\src\histogram.cpp:3439: error: (-215:Assertion failed) _src.type() == CV_8UC1 in function 'cv::equalizeHist'
这些信息说明直方图均衡处理的图像数据类型不对,因为涉及到了OpenCV的函数,在OpenCv-Python中无法深入分析,因此也没办法看出具体原因。
2、将equalizeHist函数再次封装后给fl_image调用
代码如下:
from moviepy.editor import *
import cv2
def flImg(img):imgNew = cv2.equalizeHist(img)return imgNewdef test():clipV = VideoFileClip(r"F:\video\zbl1.mp4")newclip = clipV.fl_image(cv2.equalizeHist, apply_to=['mask'])newclip.write_videofile(r"F:\video\temp\zbl1.mp4")
报错信息依旧,由于用flImg封装了cv2.equalizeHist,此时可以通过Debug跟踪flImg看到内部数据,居然发现img是三维数组,也即图像像素值是RGB格式,而不是灰度图格式。看样子虽然是个黑白视频,但格式是彩色视频格式?
3、将视频剪辑加载之后转成黑白视频再处理
好吧,黑白视频存放的是彩色格式,也许是因为视频保存格式控制的,那就把其先转成黑白视频,Moviepy有个这样变换函数blackwhite。
代码如下:
from moviepy.editor import *
import cv2def flImg(img):imgNew = cv2.equalizeHist(img)return imgNewdef test():clipV = blackwhite(VideoFileClip(r"F:\video\zbl1.mp4"))newclip = clipV.fl_image(flImg, apply_to=['mask'])newclip.write_videofile(r"F:\video\temp\zbl1.mp4")test()
结果还是报同样错,观察flImg参数img,居然还是RGB格式。黑白视频的帧是二维还是三维?让人一脸蒙圈!
4、强制图像转换为灰度图
好吧,老猿承认在黑白视频的帧数据认知上出问题了,黑白视频也是RGB格式,那只有对图像强制格式转换了,用OpenCV的cvtColor将RGB格式强制转换为灰度图。
代码如下:
from moviepy.editor import *
import cv2def flImg(img):imgNew = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)imgNew = cv2.equalizeHist(imgNew)return imgNewdef test():clipV = blackwhite(VideoFileClip(r"F:\video\zbl1.mp4"))newclip = clipV.fl_image(flImg, apply_to=['mask'])newclip.write_videofile(r"F:\video\temp\zbl1_11.mp4")newclip.write_gif(r"F:\video\temp\zbl1.gif")test()
这样处理后,执行没报错,但处理完的视频存在如下两个问题:
1、视频图像整体大小没变,但内容变成了同屏九窗播放模式:
2、视频播放速度是原视频的3倍左右,音频没变,导致视频的播放到1/3时长视频内容已经播放完成,然后视频就固定在最后一帧图像,音频还按原有节凑在播放。
总体来看,就是视频内容没有按照预定计划生成。为了查找问题,老猿做了如下处理:
- 将视频直接生成gif,gif是正常的;
- 去掉直方图均衡处理,只保留图像格式变换处理,还是存在问题;
- 去掉图像格式变换处理,flImg直接返回原图像,视频生成后是正常的。
考虑上述处理可以得出结论,问题出现与视频帧图像RGB转灰度图处理有关,结合前面彩色视频使用blackwhite转黑白视频后图像像素仍然是RGB格式的情况,老猿感觉是黑白视频的帧图像格式不是我理解的灰度图,而是RGB格式的图像。那这个过程到底该怎么处理呢?为此老猿查看了Moviepy的blackwhite的源代码:
def blackwhite(clip, RGB=None, preserve_luminosity=True):""" Desaturates the picture, makes it black and white.Parameter RGB allows to set weights for the different colorchannels.If RBG is 'CRT_phosphor' a special set of values is used.preserve_luminosity maintains the sum of RGB to 1."""if RGB is None:RGB = [1, 1, 1]if RGB == 'CRT_phosphor':RGB = [0.2125, 0.7154, 0.0721]R, G, B = 1.0 * np.array(RGB) / (sum(RGB) if preserve_luminosity else 1)def fl(im):im = (R * im[:, :, 0] + G * im[:, :, 1] + B * im[:, :, 2])a = np.dstack(3 * [im]).astype('uint8')return areturn clip.fl_image(fl)
可以看到,该源代码是先将RGB像素值按照参数指定要求转换成灰度值,最后最将该灰度值复制3份作为返回图像的RGB值,因此这个处理实际上图像最终格式是RGB格式,但RGB的值都是对应的原RGB映射到的灰度值。
5、在帧图像变换时增加将灰度图变成RGB格式的灰度图处理
为了确保最终的黑白视频格式满足像素值为RGB格式要求,对灰度变换后的图像增加了将灰度值扩展成RGB格式的处理,在此用到了numpy的dstack函数。代码如下:
from moviepy.editor import *
import cv2
import numpy as npdef flImg(img):imgNew = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)imgNew = cv2.equalizeHist(imgNew)imgNew = np.dstack(3 * [imgNew]).astype('uint8')return imgNewdef test():clipV = VideoFileClip(r"F:\video\zbl1.mp4")newclip = clipV.fl_image(flImg, apply_to=['mask'])newclip.write_videofile(r"F:\video\temp\zbl1.mp4")test()
经验证,这样处理后的视频输出文件变成了正常播放的视频,但说实话直方图均衡处理的效果并不很好,虽然视频总体变亮了,但不同帧之间图像的差异变大了,导致视频的画面连续变化较大。
五、小结
本文通过介绍将视频帧转换为灰度图像,再构建黑白视频的处理过程所遇到的问题及解决办法,确认了无论是从输入黑白视频的像素值还是将黑白视频输出到视频文件的处理过程来看,黑白视频的帧图像不是二维的灰度图,而是对应三维的彩色图像格式,其像素值为RGB三元组格式,只是R、G、B三个分量的值都是为对应灰度图的灰度值。
更多关于 Moviepy 的介绍请大家参考《Python音视频剪辑库MoviePy1.0.3中文教程导览及可执行工具下载》。
如对文章内容存在疑问或需要相关资料,可在博客评论区留言,或关注:老猿Python 微信公号发消息咨询,可通过扫二维码加微信公众号。
写博不易,敬请支持:
如果阅读本文于您有所获,敬请点赞、评论、收藏,谢谢大家的支持!
关于老猿的付费专栏
- 付费专栏《https://blog.csdn.net/laoyuanpython/category_9607725.html 使用PyQt开发图形界面Python应用》专门介绍基于Python的PyQt图形界面开发基础教程,对应文章目录为《 https://blog.csdn.net/LaoYuanPython/article/details/107580932 使用PyQt开发图形界面Python应用专栏目录》;
- 付费专栏《https://blog.csdn.net/laoyuanpython/category_10232926.html moviepy音视频开发专栏 )详细介绍moviepy音视频剪辑合成处理的类相关方法及使用相关方法进行相关剪辑合成场景的处理,对应文章目录为《https://blog.csdn.net/LaoYuanPython/article/details/107574583 moviepy音视频开发专栏文章目录》;
- 付费专栏《https://blog.csdn.net/laoyuanpython/category_10581071.html OpenCV-Python初学者疑难问题集》为《https://blog.csdn.net/laoyuanpython/category_9979286.html OpenCV-Python图形图像处理 》的伴生专栏,是笔者对OpenCV-Python图形图像处理学习中遇到的一些问题个人感悟的整合,相关资料基本上都是老猿反复研究的成果,有助于OpenCV-Python初学者比较深入地理解OpenCV,对应文章目录为《https://blog.csdn.net/LaoYuanPython/article/details/109713407 OpenCV-Python初学者疑难问题集专栏目录 》
- 付费专栏《https://blog.csdn.net/laoyuanpython/category_10762553.html Python爬虫入门 》站在一个互联网前端开发小白的角度介绍爬虫开发应知应会内容,包括爬虫入门的基础知识,以及爬取CSDN文章信息、博主信息、给文章点赞、评论等实战内容。
前两个专栏都适合有一定Python基础但无相关知识的小白读者学习,第三个专栏请大家结合《https://blog.csdn.net/laoyuanpython/category_9979286.html OpenCV-Python图形图像处理 》的学习使用。
对于缺乏Python基础的同仁,可以通过老猿的免费专栏《https://blog.csdn.net/laoyuanpython/category_9831699.html 专栏:Python基础教程目录)从零开始学习Python。
如果有兴趣也愿意支持老猿的读者,欢迎购买付费专栏。