Py学习  »  Python

Python音视频开发:消除抖音短视频Logo的图形化工具实现

LaoYuanPython • 3 年前 • 266 次点击  

☞ ░ 前往老猿Python博文目录

一、引言

在《 Python音视频开发:消除抖音短视频Logo和去电视台标的实现详解 》节介绍了怎么通过Python+Moviepy+OpenCV实现消除视频Logo的四种方法,并提供了详细的实现思路和实现代码,但这种原生态的应用不适合开发人员以外的其他人员使用,提供一个图形界面的工具程序是比较好的解决方案,本文就介绍实现这样一个图形化工具的步骤。

本节的背景知识都在《 Python音视频开发:消除抖音短视频Logo和去电视台标的实现详解 》介绍了,在此就不重复介绍了。

二、图形界面设计

本程序复用了《 PyQt+moviepy音视频剪辑实战1:多个音视频合成顺序播放或同屏播放的视频文件实现详解 》、《 PyQt+moviepy音视频剪辑实战1:多视频合成顺序播放或同屏播放的视频文件 》的公用框架,该框架提供统一的print输出管理、浮动窗口管理以及系统统一框架。

2.1、主界面

在这里插入图片描述
从上面截图可以看到,主界面处理公共框架的功能外,提供了三大类功能,分别是消除准备(包括选择Logo、选择替换图)、查看功能(包括查看Logo图、查看替换图)、视频Logo消除(包括基于帧的预览、视频预览以及视频输出)。

2.2、图形化信息预览窗

该窗口可以用于预览图像及信息的展现,使用QGraphicView来实现:
在这里插入图片描述

三、程序实现

3.1、主界面派生类定义及相关初始化方法

class mainWin(QtWidgets.QMainWindow,ui_mainWin.Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.initValues()

        self.initSignalAndSlots()
        self.initPublicFrame()

    def initWidgetSatus(self):#初始化界面元素状态
        self.action_selectReplaceRegion.setEnabled(False)
        self.action_selectLog.setEnabled(False)
        self.action_previewOneFrame.setEnabled(False)
        self.action_viewLogoImg.setEnabled(False)
        self.action_videoPreview.setEnabled(False)
        self.action_viewReplaceImg.setEnabled(False)
        self.action_outputVideo.setEnabled(False)

    def initOperations(self):#当选择不同的视频文件时要作废原视频操作的相关记录
        self.logoSelected = False
        self.replaceRegSelected =False
        if self.srcFName:
            self.videoOperation = CSubVideoImg(self.srcFName)

        self.replaceObject = None
        self.logoObjList = None
        self.destFNameManuChanged = False


    def initValues(self):#部分实例变量初始化
        self.initWidgetSatus()

        self.videoOperation = None
        self.srcFName = None
        self.destFName = None
        self.srcDir = ""
        self.destDir = ""
        self.showHelpInfo = True #是否显示操作提示信息
        self.destFNameManuChanged = False #输出文件是否手工修改标记
        self.ridLogoManner = ridLogoManner_inpaint #缺省消除方式
        self.imgInfW = imgInfoWin.imgInfoWin() #创建图像信息展示窗
        self.fileDialog = QtWidgets.QFileDialog(self)
        self.initOperations()


    def initPublicFrame(self):


    
#公共框架初始化
        self.toggleOperationInfObject = self.actionshowHideOpInf  # 显隐操作信息窗的开关对象如按钮、动作等,必须可以使用setText方法
        self.connectShowInfoSignal = self.actionshowHideOpInf.triggered  # 用于触发打开或关闭输出信息窗的信号
        self.connectAboutSignal = self.actionAbout.triggered  # 用于触发打开about提示窗的信号
        self.needShowHints = False  # 如果需要在运行窗口显示初始的操作提示信息,则将此置为True,并在本类中提供showOperationHints(displayMsgWin)实例方法

    def initSignalAndSlots(self): #信号和槽连接,所有重要操作都会重新触发界面元素状态设置
        self.btn_choiceSrc.clicked.connect(self.chooseFile) #选择源文件
        self.videoFile.textChanged['QString'].connect(self.verifyWidgetStatus)
        self.destFile.textEdited.connect(self.verifyWidgetStatus) #目标文件修改了
        self.btn_choiceDest.clicked.connect(self.chooseFile)
        self.destFile.textEdited.connect(self.manuChangeDestFName) #输出文件如果手工修改了则不会根据选择的视频文件自动同步

        self.action_selectLog.triggered.connect(self.selectLog) #选择Logo区域信号连接
        self.action_previewOneFrame.triggered.connect(self.previewOneFrame) #帧预览信号连接
        self.action_videoPreview.triggered.connect(self.videoPreview) #视频预览信号连接
        self.action_help.triggered.connect(self.help) #帮助信号连接
        self.action_selectReplaceRegion.triggered.connect(self.selectReplaceRegion) #替换图像选择信号连接
        self.action_viewLogoImg.triggered.connect(self.viewLogoImg) #查看Logo图像信号连接
        self.action_viewReplaceImg.triggered.connect(self.viewReplaceImg) #查看替换图像信号连接
        self.action_outputVideo.triggered.connect(self.convertVideo) #输出处理视频信号连接

        #消除方式变化信号连接
        self.radioButton_staticImg.toggled.connect(self.ridLogoMannerSelected)
        self.radioButton_frameImg.toggled.connect(self.ridLogoMannerSelected)
        self.radioButton_inpaint.toggled.connect(self.ridLogoMannerSelected)
        self.radioButton_multiSampleInpaint.toggled.connect(self.ridLogoMannerSelected)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

3.2、信息显示方法

 def showHelp(self,helpinf): #显示帮助信息
        print(helpinf)
        if self.checkBox_showOperHint.isChecked():
            QMessageBox.information(self, '操作提示', helpinf, QMessageBox.Ok )

    def showInfo(self,info):#显示提示信息
        print(info)
        QMessageBox.information(self, '操作提示', info, QMessageBox.Ok )

    def showImgInf(self,*showObjs): #在图像信息窗按顺序显示相关图像或文本
        imgInfW = self.imgInfW
        for showObj in showObjs:
            if showObj is None: continue
            if isinstance(showObj,str):
                imgInfW.showText(showObj)
            else:
                qtimg = imgInfW.showCVImg(showObj)

        imgInfW.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

3.3、部件状态设置方法

    def verifyWidgetStatus(self): #根据当前操作确认界面元素的状态
        self.initWidgetSatus()

        videoFName = self.videoFile.text().strip()
        if len(videoFName):
            dir = QtCore.QDir("")
            if(dir.exists(videoFName)):
                self.action_selectLog.setEnabled(True)
                if self.ridLogoManner in [ridLogoManner_staticImg,ridLogoManner_frameImg]:
                    if self.logoSelected:
                        self.action_selectReplaceRegion.setEnabled(True)
                if videoFName!=self.srcFName:
                    self.srcFName = videoFName
                    self.initOperations()

                if self.logoSelected and  (self.replaceRegSelected or self.ridLogoManner in [ridLogoManner_inpaint,ridLogoManner_multiSampleInpaint] ):
                    self.action_previewOneFrame.setEnabled(True)
                    self.


    
action_videoPreview.setEnabled(True)
                    destFName = self.destFile.text().strip()
                    if len(destFName):
                        self.action_outputVideo.setEnabled(True)
                        self.destFName = destFName

                if  self.replaceRegSelected:
                    if self.ridLogoManner in [ridLogoManner_staticImg,ridLogoManner_frameImg]:
                        self.action_viewReplaceImg.setEnabled(True)

                if self.logoSelected:
                    self.action_viewLogoImg.setEnabled(True)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

3.4、消除准备相关动作槽方法示例

下面是准备消除操作的一个关键槽方法–选择Logo图像的槽方法:

   
    def selectLog(self): #实现Logo图像选择
        fps = int(self.lineEdit_logoSelectFps.text().strip())

        if self.ridLogoManner != ridLogoManner_multiSampleInpaint:
            helpstr = "将弹出新窗口按设定的帧率播放视频图像,请在显示的视频上使用鼠标左键选择Logo图像的范围。注意:\n" + \
                      "1、选择时会有蓝色边框的矩形确认选择范围,当选择完成时松开鼠标即可确认选择;\n" + \
                      "2、视频选择时会停止播放,可以选择完成后通过鼠标右键点击或鼠标双击恢复视频播放\n" + \
                      "3、如果选择错了可以重新选择;\n" + \
                      "4、如果确认选择结束,按ESC或Q、q三者中的一个退出选择操作,系统将记录选择的Logo图像;\n" + \
                      "5、选择的Logo图像可以通过查看菜单下的相关菜单进行查看。\n\n" + \
                      "本提示信息可以通过界面“显示操作提示信息”复选框关闭。"
        else:
            helpstr = "将弹出新窗口按设定的帧率播放视频图像,请在显示的视频上使用鼠标左键选择Logo图像的范围。注意:\n" + \
                      "1、选择时会有蓝色边框的矩形确认选择范围,当选择完成时松开鼠标即可确认选择;\n" + \
                      "2、视频选择时会停止播放,可以选择完成后通过鼠标右键点击或鼠标双击恢复视频播放\n" + \
                      "3、如果选择错了可以重新选择;\n" + \
                      "4、如果确认选择,按n、N、s、S将保存当前选择Logo图像,恢复播放后可以再选择Logo再保存,以支持选择多个Logo图像" \
                      "5,按ESC或Q、q三者中的一个退出选择操作,退出时已选择图像会保存,系统将不剔重的记录选择的所有Logo图像;\n" + \
                      "6、选择的Logo图像可以通过查看菜单下的相关菜单进行查看。\n\n" + \
                      "本提示信息可以通过界面“显示操作提示信息”复选框关闭。"
        self.showHelp(helpstr)
        logobjs,frame = self.videoOperation.getROI("select multiLogo Imgs Range",fps)

        if logobjs is not None and  len(logobjs):
            self.logoSelected = True
            self.logoObjList = (logobjs, frame)
            self.verifyWidgetStatus()
            self.frameMask = self.videoOperation.genMultiLogoFrameMask([logobjs[-1]],frame)
            self.multiFrameMask = self.videoOperation.genMultiLogoFrameMask(logobjs, frame)
            self.frame = frame
        else:
            self.showInfo("本次操作没有选择对应Logo图像,如果要执行后续操作,请重新选择。")


    


   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

3.5、查看动作槽方法示例

    def viewLogoImg(self): #查看Logo图像
        if not self.logoSelected:
            self.showInfo("当前视频文件尚未选择Logo图像")
            return

        self.showImgInf( f"截取的Logo共计{len(self.logoObjList[0])}个,除了多采样图像修复术外,其他消除方法都只取最后一个。各图像如下:\n ")
        count = 0
        logoObjs = self.logoObjList[0]

        for logoobj in logoObjs:
            self.showImgInf(f"    第{count + 1}个:",logoobj[0])
            count += 1

   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3.6、视频输出槽方法

    
    def convertVideo(self): #输出视频
        self.setEnabled(False)
        if self.ridLogoManner in [ridLogoManner_staticImg,ridLogoManner_frameImg]:
            ret, inf = self.videoOperation.convertVideo(self.destFName, self.ridLogoManner, self.logoObjList, self.replaceObject)

        elif self.ridLogoManner == ridLogoManner_inpaint:
            ret,inf = self.videoOperation.convertVideo( self.destFName, self.ridLogoManner, self.logoObjList, frameMask=self.frameMask)
        else:
            ret,inf = self.videoOperation.convertVideo( self.destFName, self.ridLogoManner, self.logoObjList, frameMask=self.multiFrameMask)
        print(inf)

        self.setEnabled(True)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

四、主程序代码

if __name__=='__main__':
    app = QtWidgets.QApplication(sys.argv)
    w = mainWin()
    loadWin = loadApp.loadAppWin(w,"视频Logo消除"


    
, True, True)
    w.show()
    sys.exit(app.exec_())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

五、运行截图

1、初始界面
在这里插入图片描述

2、选择Logo图像的截图
在这里插入图片描述
3、查看Logo图像的截图
在这里插入图片描述
4、输出视频文件截图
在这里插入图片描述

六、打包成exe

使用《 PyQt(Python+Qt)学习随笔:windows下使用pyinstaller将PyQt文件打包成exe可执行文件 》介绍的方法进行打包。

老猿在win7上最终打包的可执行程序包已经上传到百度云,大家可以下载下来长期免费使用。具体下载地址为百度网盘。

链接:https://pan.baidu.com/s/1UNaA2UqQBoxx-v8rCIPDhA

提取码:yh2d

选择该链接下的: 视频Logo消除工具V2.0.rar 即可。

注意:
百度云上分享的《咖啡狗免费工具软件共享空间》下的不同软件安装时必须解压到不同目录,如果解压到同一目录可能有冲突导致不能正常运行,但解压后遵循如下要求可以将其聚合到同一个目录:

  1. 放置到同一目录的不同软件的版本必须相同,版本为压缩文件名中VX.X标注;
  2. 聚合拷贝时除拷贝执行文件外,还有resource目录必须拷贝,如果resource目录下有相同文件名可以覆盖;
  3. 聚合拷贝exe文件和resource目录及其下文件到其他已解压工具目录后,源目录可以删除。

更多moviepy的介绍请参考《 PyQt+moviepy音视频剪辑实战文章目录 》或《 moviepy音视频开发专栏 》。这2个专栏内容的导读请参考《 Python音视频剪辑库MoviePy1.0.3中文教程导览及可执行工具下载 》。

关于老猿的付费专栏

老猿的付费专栏《 使用PyQt开发图形界面Python应用 》专门介绍基于Python的PyQt图形界面开发基础教程,付费专栏《 moviepy音视频开发专栏 》详细介绍moviepy音视频剪辑合成处理的类相关方法及使用相关方法进行相关剪辑合成场景的处理,两个专栏加起来只需要19.9元,都适合有一定Python基础但无相关专利知识的小白读者学习。这2个收费专栏都有对应免费专栏,只是收费专栏的文章介绍更具体、内容更深入、案例更多。

付费专栏文章目录 :《 moviepy音视频开发专栏文章目录 》、《 使用PyQt开发图形界面Python应用专栏目录 》。本文对应付费专栏文章为《 Python音视频开发:消除抖音短视频Logo的图形化工具实现过程详解 》。

关于Moviepy音视频开发的内容,请大家参考《 Python音视频剪辑库MoviePy1.0.3中文教程导览及可执行工具下载 》的导览式介绍。

对于缺乏Python基础的同仁,可以通过老猿的免费专栏《 专栏:Python基础教程目录 》从零开始学习Python。

如果有兴趣也愿意支持老猿的读者,欢迎购买付费专栏。

跟老猿学Python!

☞ ░ 前往老猿Python博文目录

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/73304
 
266 次点击