对于带Logo(如抖音Logo、电视台标)的视频,有三种方案进行Logo消除:html
其中:
方法1又可使用三种方法,一是使用某固定图像替换、二是截取视频某帧的一部分图像替换、三是用每帧固定区域的图像替换当前帧的Logo区域,其中固定图像替换最简单,下面就不展开介绍;截取视频某帧的一部分图像比较简单,用每帧固定区域的图像替换当前帧的Logo区域最复杂;python
方法2能够认为是方法3的特例,即填充值来源于简单计算,如Logo区域像素的均值等,咱们在此不进行介绍。web
方法3是以Logo去除后根据原Logo区域附近的图像像素对Logo区域进行插值填充,以确保填充后的图像总体比较协调、完整。算法
能够经过cv2.imshow(winname, img)
来显示一个图片,当读取视频文件的帧图片连续显示时就是一个无声的视频播放。其中的参数winname为一个英文字符串,显示为窗口的标题,OpenCV将其做为窗口的名字,做为识别窗口的标识,相同名字的窗口就是同一个窗口。数组
对于相关窗口,OpenCV提供鼠标及键盘事件处理机制。app
OpenCV提供了设置鼠标事件回调函数来提供鼠标事件处理的机制,设置回调函数的方法以下:
cv2.setMouseCallback(winName, OnMouseFunction, param)
其中winName为要设置鼠标回调处理的窗口名,OnMouseFunction为回调函数,用于处理鼠标响应,param为设置回调函数时传入的应用相关特定参数,能够不设置,但须要在回调函数访问设置回调函数对象属性时很是有用。less
OpenCV提供了在图像中绘制几何图形的方法,绘制的图像包括矩形、椭圆、扇形、弧等。本文主要介绍矩形的绘制,具体调用语法以下:ide
rectangle(img, pt1, pt2, color, thickness=None, lineType=None, shift=None)
其中参数:svg
另外该方法还有个变种调用方式:
rectangle(img, rec, color[, thickness[, lineType[, shift]]])
,其中的rec为上面pt1和pt2构建的矩形。函数
fl_image方法为moviepy音视频剪辑库提供的视频剪辑类VideoClip的视频变换方法,具体请参考《moviepy音视频剪辑:视频剪辑基类VideoClip的属性及方法详解》。
在python中可使用全局变量,关于全局变量的使用请参考《 Python函数中的变量及做用域》的介绍。
OpenCV中的cv2.inpaint()函数使用插值方法修复图像,调用语法以下:
dst = cv2.inpaint(src,mask, inpaintRadius,flags)
参数含义以下:
cv2.cvtColor是openCV提供的颜色空间转换函数,调用语法以下:
cvtColor(src, code, dstCn=None)
其中:
openCV图像的阈值处理又称为二值化,之因此称为二值化,是它能够将一幅图转换为感兴趣的部分(前景)和不感兴趣的部分(背景)。转换时,一般将某个值(即阈值)看成区分处理的标准,一般将超过阈值的像素做为前景。
阈值处理有2种方式,一种是固定阈值方式,又包括多种处理模式,另外一种是非固定阈值,由程序根据算法以及给出的最大阈值计算图像合适的阈值,再用这个阈值进行二值化处理,非固定阈值处理时须要在固定阈值处理基础上叠加组合标记。
调用语法:
retval, dst = cv2.threshold (src, thresh, maxval, type)
其中:
案例:
ret, mask = cv2.threshold(img, 35, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
补充说明:
关于膨胀处理的知识解释有点复杂,请参考《OpenCV-Python学习—形态学处理》以及《Opencv python 锚点anchor位置及borderValue的改变对膨胀腐蚀的影响》。
图像的膨胀处理会使得图像中较亮的区域增大,较暗的区域减少。
本部分介绍的内容对Logo去除采用了以下四种方式:
以上四种处理方式,对应的消除Logo方法类型分别为:
ridLogoManner_staticImg = 1 ridLogoManner_frameImg = 2 ridLogoManner_inpaint = 3 ridLogoManner_multiSampleInpaint = 4
为了实现Logo标记的消除,具体步骤以下:
这是一个比较通用的鼠标回调函数,代码以下:
def OnMouseEvent( event, x, y, flags, param): try: mouseEvent = param mouseEvent.processMouseEvent(event, x, y, flags) except Exception as e: print("使用回调函数OnMouseEvent的方法错误,全部使用该回调函数处理鼠标事件的对象,必须知足以下条件:") print(" 一、必须将自身经过param传入") print(" 二、必须定义一个processMouseEvent(self)方法来处理鼠标事件") print(e)
全部使用该回调函数处理鼠标事件的对象,必须将自身经过param传入到回调函数中,而且必须定义一个processMouseEvent(self)方法来处理鼠标事件。下面介绍的类CImgMouseEvent就是知足条件的类。
为了支持在视频图像中进行相关操做,须要比较方便的支持并识别鼠标操做的类,在此称为CImgMouseEvent, CImgMouseEvent用于OpenCV显示图像的窗口的鼠标事件处理,会记录下前一次鼠标左键按下或释放的位置以及操做类型,并记录下当前鼠标移动、左键按下或释放事件的信息。
以上鼠标事件属性的记录处理都在CImgMouseEvent的方法processMouseEvent中,但processMouseEvent方法仅记录鼠标事件属性,记录后调用父对象的 parent的processMouseEvent方法实现真正的操做
class CImgMouseEvent(): def __init__(self,parent,img=None,winName=None): self.img = img self.winName = winName self.parent = parent self.ignoreEvent = [cv2.EVENT_MBUTTONDOWN,cv2.EVENT_MBUTTONUP,cv2.EVENT_MBUTTONDBLCLK,cv2.EVENT_MOUSEWHEEL,cv2.EVENT_MOUSEHWHEEL] #须要忽略的鼠标事件 self.needRecordEvent = [cv2.EVENT_MOUSEMOVE,cv2.EVENT_LBUTTONDOWN,cv2.EVENT_LBUTTONUP] #须要记录当前信息的鼠标事件 self.windowCreated = False #窗口是否建立标记 if img is not None:self.showImg(img,winName) self.open(winName) def open(self, winName=None): #初始化窗口相关属性,通常状况下此时窗口还未建立,所以鼠标回调函数设置不会执行 if winName: if self.winName != winName: if self.winName: cv2.destroyWindow(self.winName) self.windowCreated = False self.WinName = winName self.mouseIsPressed = self.playPaused = False self.previousePos = self.pos = self.previousEvent = self.event = self.flags = self.previouseFlags = None if self.winName and self.windowCreated : cv2.setMouseCallback(self.winName, OnMouseEvent, self) def showImg(self,img,winName=None): """ 在窗口winName中显示img图像,并设置鼠标回调函数为OnMouseEvent """ if not winName:winName = self.winName self.img = img if winName != self.winName: self.winName = winName self.open() if not self.windowCreated: self.windowCreated = True cv2.namedWindow(winName)#cv2.WINDOW_NORMAL| cv2.WINDOW_KEEPRATIO | cv2.WINDOW_GUI_EXPANDED cv2.setMouseCallback(winName, OnMouseEvent, self) cv2.imshow(winName, img) def processMouseEvent(self,event, x, y, flags): #鼠标回调函数调用该函数处理鼠标事件,包括记录当前事件信息、判断是否记录上次鼠标事件信息、是否暂停视频播放,调用parent.processMouseEvent() 执行响应操做 #mouseventDict = {cv2.EVENT_MOUSEMOVE:"鼠标移动中",cv2.EVENT_LBUTTONDOWN:"鼠标左键按下",cv2.EVENT_RBUTTONDOWN:"鼠标右键按下",cv2.EVENT_MBUTTONDOWN:"鼠标中键按下",cv2.EVENT_LBUTTONUP:"鼠标左键释放",cv2.EVENT_RBUTTONUP:"鼠标右键释放",cv2.EVENT_MBUTTONUP:"鼠标中键释放",cv2.EVENT_LBUTTONDBLCLK:"鼠标左键双击",cv2.EVENT_RBUTTONDBLCLK:"鼠标右键双击",cv2.EVENT_MBUTTONDBLCLK:"鼠标中键双击",cv2.EVENT_MOUSEWHEEL:"鼠标轮上下滚动",cv2.EVENT_MOUSEHWHEEL:"鼠标轮左右滚动"} #print(f"processMouseEvent {mouseventDict[event]} ") if event in self.ignoreEvent:return if self.event in [cv2.EVENT_LBUTTONDOWN,cv2.EVENT_LBUTTONUP]:#当上次鼠标事件左键按下或释放时,上次信息保存 self.previousEvent,self.previousePos,self.previouseFlags = self.event,self.pos,self.flags if event==cv2.EVENT_LBUTTONUP: self.mouseIsPressed = False elif event == cv2.EVENT_LBUTTONDOWN: self.mouseIsPressed = True self.playPaused = True elif event in [cv2.EVENT_LBUTTONDBLCLK,cv2.EVENT_RBUTTONDBLCLK,cv2.EVENT_RBUTTONDOWN,cv2.EVENT_RBUTTONUP]:#鼠标右键动做、鼠标双击动做恢复视频播放 self.playPaused = False if event in self.needRecordEvent: self.event,self.flags,self.pos = event,flags,(x,y) self.parent.processMouseEvent() #调用者对象的鼠标处理方法执行 def getMouseSelectRange(self): """ 获取鼠标左键按下位置到当前鼠标移动位置或左键释放位置的对应的矩形以及矩形最后位置的鼠标事件类型 :return: 由鼠标左键按下开始到鼠标左键释放或鼠标当前移动位置的矩形,为None表示当前没有这样的操做 """ if self.previousEvent is None or self.event is None: return None if (self.event!=cv2.EVENT_LBUTTONUP) and (self.event!=cv2.EVENT_MOUSEMOVE): #最近的事件不是鼠标左键释放或鼠标移动 return None if self.pos == self.previousePos:#与上次比位置没有变化 return None if (self.previousEvent== cv2.EVENT_LBUTTONDOWN ) and (self.event==cv2.EVENT_LBUTTONUP): #鼠标左键按下位置到鼠标左键释放位置 return [self.previousePos,self.pos,cv2.EVENT_LBUTTONUP] elif (self.previousEvent== cv2.EVENT_LBUTTONDOWN ) and (self.event==cv2.EVENT_MOUSEMOVE):#鼠标左键按下位置到鼠标当前移动位置 return [self.previousePos, self.pos, cv2.EVENT_MOUSEMOVE] return None def drawRect(self,color,specRect=None,filled=False): """ :param color: 矩形颜色 :param specRect: 不为None画specRect指定矩形,不然根据鼠标操做来判断 :param filled: 是画实心仍是空心矩形,缺省为空心矩形 :return: 画下的矩形,specRect不为None时是specRect指定矩形,不然根据鼠标操做来判断 """ if specRect: rect = specRect else: rect = self.getMouseSelectRange() if rect: img = self.img img = self.img.copy() if not filled: cv2.rectangle(img, rect[0], rect[1], color,1) else: cv2.rectangle(img, rect[0], rect[1], color,-1) cv2.imshow(self.winName, img) return rect else: return None def drawEllipse(self, color,specRect=None, filled=False): """ :param color: 椭圆颜色 :param specRect: 不为None画specRect指定椭圆,不然根据鼠标操做来判断 :param filled: 是画实心仍是空心椭圆,缺省为空心椭圆 :return: 画下的椭圆对应的外接矩形,specRect不为None时是specRect指定矩形,不然根据鼠标操做来判断 """ if specRect: rect = specRect else: rect = self.getMouseSelectRange() if rect: x0, y0 = rect[0] x1, y1 = rect[1] x = int((x0+x1)/2) y = int((y0+y1)/2) axes = (int(abs(x1-x0)/2),int(abs(y1-y0)/2)) img = self.img.copy() if not filled: cv2.ellipse(img, (x, y),axes, 0,0,360, color,1) else: cv2.ellipse(img, (x, y),axes, 0,0,360, color,-1) cv2.imshow(self.winName, img) return rect else: return None def close(self): cv2.destroyWindow(self.winName) self.windowCreated = False def __del__(self): self.close()
CSubVideoImg类用于操做视频及视频的图像,主要用于对一个视频的帧图像进行操做。
class CSubVideoImg(): def __init__(self,videoFName): super().__init__() self.imgMouseEvent = CImgMouseEvent(self) #建立鼠标事件处理对象 self.videoFName = videoFName self.exitKeys = [ord('q') ,ord('q'), 27] #视频图像播放时退出键定义,包括q、Q以及ESC self.initStatus() def initStatus(self):#初始化相关变量,self.rect为记录最后一个鼠标选择框 self.rect = self.logoObjList = self.replaceObject = self.frameMask = None def processMouseEvent(self):#鼠标事件响应函数,将当前选择框显示出来 self.drawSelectRange() def drawSelectRange(self,specRect=None): if specRect: rect = self.imgMouseEvent.drawRect((255, 0, 0),specRect) else: rect = self.imgMouseEvent.drawRect((255, 0, 0)) if rect: self.rect = rect def displayImg(self,winname,img,seconds): cv2.imshow(winname, img) ch = cv2.waitKey(seconds*1000) cv2.destroyWindow(winname) def getROI(self, operInfo, fps=24): """ 获取视频中的多个ROI区域(即鼠标选择区域),选定一个ROI区域后,按N、n、S、s保存当前选择区域 按指定帧率播放视频(仅图像),并提供在视频图像中选中某个矩形范围,并在接下来播放中一直显示该矩形,按EsC或q或Q退出 退出后会显示当前选择的ROI图像 :param operInfo: 播放窗口提示信息,也即窗口名,必须是英文 :param fps: 播放的帧率 :return: 返回选择的ROI及对应帧的二元组,相似:([(rect1,img1),...,(rectn,imgn)],frame)矩形和最后选中操做所在帧的选择图像 """ frame = None cap = cv2.VideoCapture(self.videoFName) self.imgMouseEvent.open(operInfo) ROIList = [] saveKeys = [ord('n'), ord('N'), ord('s'), ord('S')]+self.exitKeys #保存和退出键都保存最后一个选择矩阵范围 self.rect = None if not cap.isOpened(): print("Cannot open video") return None while True: if not self.imgMouseEvent.playPaused: #正在播放 ret, frame = cap.read() if not ret: if frame is None: print("The video has end.") else: print("Read video error!") break self.imgMouseEvent.showImg(frame, operInfo) self.drawSelectRange(self.rect) ch = cv2.waitKey(int(1000 / fps)) if ch in saveKeys: if self.rect is not None: x0, y0 = self.rect[0] x1, y1 = self.rect[1] ROI = frame[y0:y1, x0:x1] ROIList.append((ROI, self.rect)) self.rect = None if ch in self.exitKeys: break # 完成全部操做后,释放捕获器 if len(ROIList) == 0: self.imgMouseEvent.close() cap.release() return None,None self.imgMouseEvent.close() cap.release() return ROIList, frame def replaceImgRegionBySpecImg(self,srcImg,regionTopLeftPos,specImg): """ 将srcImg的regionTopLeftPos开始位置的一个矩形图像替换为specImg :return: True 成功,False失败 """ srcW, srcH = srcImg.shape[1::-1] refW, refH = specImg.shape[1::-1] x,y = regionTopLeftPos if (refW>srcW) or (refH>srcH): #raise ValueError("specImg's size must less than srcImg") print(f"specImg's size {specImg.shape[1::-1]} must less than srcImg's size {srcImg.shape[1::-1]}") return False else: srcImg[y:y+refH,x:x+refW] = specImg return True def replaceImgRegionBySpecRange(self,srcImg,regionTopLeftPos,specRect): """ 将srcImg的regionTopLeftPos开始位置的一个矩形图像替换为srcImg内specRect指定的一个矩形范围图像 :return: True 成功,False失败 """ srcW, srcH = srcImg.shape[1::-1] refW, refH = specRect[1][0]-specRect[0][0],specRect[1][1]-specRect[0][1] x,y = regionTopLeftPos if (refW>srcW) or (refH>srcH): print(f"specImg's size {(refW, refH)} must less than srcImg's size {srcImg.shape[1::-1]}") return False else: srcImg[y:y+refH,x:x+refW] = srcImg[specRect[0][1]:specRect[1][1],specRect[0][0]:specRect[1][0]] return True def adjuestImgAccordingRefImg(self,img,refimg,color=None): """ 按照refimg大小调整img大小,若是是扩充,则采用img边缘像素的镜像复制或指定颜色建立扩充像素 :param img: :param refimg: :param color: :return: """ srcW,srcH = img.shape[1::-1] refW,refH = refimg.shape[1::-1] if srcW>refW: diff = int((srcW-refW)/2) img = img[:,diff:refW+diff] if srcH>refH: diff = int((srcH - refH) / 2) img =img[diff:refH+diff,:] srcW, srcH = img.shape[1::-1] w = max(srcW,refW) h = max(srcH,refH) diffW = int((w-srcW)/2) diffH = int((h-srcH)/2) if color is None: dest = cv2.copyMakeBorder(img,diffH,h-srcH-diffH,diffW,w-srcW-diffW,cv2.BORDER_REFLECT_101) #上下左右扩展当前图像 else: dest = cv2.copyMakeBorder(img, diffH,h-srcH-diffH,diffW,w-srcW-diffW, cv2.BORDER_CONSTANT,color)#上下左右扩展当前图像,扩展部分颜色为color rectSize = (h,w) return dest def createImgMask(self, img): # 建立img的掩码 img2gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, mask = cv2.threshold(img2gray, 35, 255, cv2.THRESH_BINARY) #转为像素值为0和255的二值图,阈值为35 #对掩码进行膨胀处理 element = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) mask = cv2.dilate(mask, element) return mask def genLogoFrameMask(self,frame,logoObject): #将Logo掩码填充到一与视频帧大小相同的全0图像中 logoImg,logoRect = logoObject if logoImg is None: return None else: logMask = self.createImgMask(logoImg) frameMask = np.zeros(frame.shape[0]*frame.shape[1],dtype=np.uint8) frameMask = frameMask.reshape(frame.shape[0:2]) x0,y0 = logoRect[0] x1,y1 = logoRect[1] frameMask[y0:y1,x0:x1] = logMask return frameMask def genMultiLogoFrameMask(self, logoObjectList,frame ): #将屡次采样的Logo掩码填充到一与视频帧大小相同的全0图像中 composeFrameMask = None for logoObject in logoObjectList: frameMask = self.genLogoFrameMask(frame, logoObject) if composeFrameMask is None: composeFrameMask = frameMask else: composeFrameMask = cv2.add(composeFrameMask, frameMask) return composeFrameMask def convertVideo(self,outPutFName,ridLogoManner,logoObjects,replaceObject=None,frameMask=None): #生成视频 global videoImgConvertParams if ridLogoManner in [ridLogoManner_staticImg,ridLogoManner_frameImg]: if replaceObject is None: return False,"替换图像还没有提供,请先选择替换图像" else: if frameMask is None: return False,"替换frameMask还没有提供或未生成,请确保进行了Logo图像的截取操做,请先提供" self.frameMask = frameMask self.replaceObject = replaceObject self.logoObjList = logoObjects self.ridLogoManner = ridLogoManner try: videoImgConvertParams = self, ridLogoManner clipVideo = VideoFileClip(self.videoFName) newclip = clipVideo.fl_image(processImg) newclip.write_videofile(outPutFName, threads=8) clipVideo.close() newclip.close() except Exception as e: return False,f"生成视频时出现异常:\n{e}" else: return True,f"视频处理完成,生成的视频保存着在文件:{outPutFName}" def previewVideoByReplaceLogo(self,fps,logoObjects,replaceObject,ridLogoManner): """ 使用替换区域或替换图像替换logo区域后的视频效果预览 fps:fps 用于使用静态图像或同帧图像替换后预览视频使用 :param logoObjects: 二元组:(logoObjectList,FRAME),实际形如([(logoImg1,logoRect1),...,(logoImgn,logoRectn)],FRAME) logoObjectList:列表,1...n个元素(只有当采用屡次采样修复算法时才会n大于1),每一个元素是个二元组,每一个二元组表示一个logo图像信息,包括图像的数组以及图像的位置及大小等信息, 形如:[(logoImg1,logoRect1),...,(logoImgn,logoRectn)] Frame:截取Logon图像的帧对应数组,当预览一个帧时可使用 :param replaceObject:四元组(replaceImg, replaceRect,targetReplaceImg frame) :param ridLogoManner:消除logo的方式 :return: """ global videoImgConvertParams videoImgConvertParams = self,ridLogoManner self.frameMask = None self.replaceObject = replaceObject self.logoObjList = logoObjects self.ridLogoManner = ridLogoManner cap = cv2.VideoCapture(self.videoFName) if not cap.isOpened(): print("Cannot open video") return winName = f"video previewing fps={fps}" while True: ret, frame = cap.read() if not ret: if frame is None: print("The video has end.") else: print("Read video error!") break frame = processImg(frame) cv2.imshow(winName, frame) ch = cv2.waitKey(int(1000 / fps)) if ch in self.exitKeys: break # 完成全部操做后,释放捕获器 cap.release() cv2.destroyWindow(winName) def previewVideoByInpaintLogo(self,fps, logoObjects,frameMask, ridLogoManner): """ 使用图像修复术对logo区域处理后的视频效果预览 fps:fps :param logoObjects:列表,1...n个元素(当屡次采样Logo时n大于1),每一个元素是个二元组,每一个二元组表示一个logo图像信息,包括图像的数组以及图像的位置及大小等信息, 形如:[(logoImg1,logoRect1),...,(logoImgn,logoRectn)] Frame:截取Logon图像的帧对应数组,当预览一个帧时可使用 :param ridLogoManner:消除logo的方式 """ global videoImgConvertParams if ridLogoManner not in [ridLogoManner_inpaint, ridLogoManner_multiSampleInpaint]: print("ridLogoManner is not fit previewVideoByInpaintLogo ") return False videoImgConvertParams = self, ridLogoManner self.frameMask = None self.replaceObject = None self.logoObjList = logoObjects self.ridLogoManner = ridLogoManner winName = f"video previewing,fps={fps}" self.frameMask = frameMask self.multiFrameMask = frameMask cap = cv2.VideoCapture(self.videoFName) if not cap.isOpened(): print("Cannot open video") return while True: ret, frame = cap.read() if not ret: if frame is None: print("The video has end.") else: print("Read video error!") break frame = processImg(frame) cv2.imshow(winName, frame) ch = cv2.waitKey(int(1000 / fps)) if ch in self.exitKeys: break # 完成全部操做后,释放捕获器 cap.release() cv2.destroyWindow(winName)
上面相关定义的与视频预览、帧预览等方法定义时的参数包括了记录下完整Logo采用对象、替换对象、以及Logo掩码等,这些数据须要在操做视频图像时记录并在视频处理时传递给上述方法。
上面视频图像处理类中使用了processImg函数,该函数用于视频生成的帧图像处理函数,用静态图像或同帧区域范围图像替换,或使用图像修复术修复。
在processImg函数中,使用了全局变量来传递该函数调用时的CSubVideoImg类对象及Logo消除的方式。具体实现就二十行代码,你们能够参考视频变换的介绍本身去实现,在此就不提供了,不然就和付费专栏文章彻底同样了。
主程序根据Logo消除类型来显示视频执行Logo图像选择、替换图像选择(前2种Logo消除类型)后,将视频进行消除处理。
def main(ridLogoManner): videoOperation = CSubVideoImg(r"f:\video\mydream.mp4") destFName = r"f:\video\mydream_new_"+str(ridLogoManner)+".mp4" fps = 24 replaceObject = logoObjList = multiFrameMask = frameMask = None print("请在播放的视频中选择要去除Logo的区域:") logobjs, frame = videoOperation.getROI("select multiLogo Imgs Range", fps) if logobjs is not None and len(logobjs): logoObjList = (logobjs, frame) frameMask = videoOperation.genMultiLogoFrameMask([logobjs[-1]], frame) multiFrameMask = videoOperation.genMultiLogoFrameMask(logobjs, frame) frame = frame else: print("本次操做没有选择对应Logo图像,程序退出。") return if ridLogoManner in ( ridLogoManner_staticImg, ridLogoManner_frameImg): # ridLogoManner_inpaint , ridLogoManner_multiSampleInpaint print("请在播放的视频中选择要去除Logo的区域:") replaceObjList, frame = videoOperation.getROI("select Replace Img Range") if replaceObjList is None: replaceObject = None print("本次操做没有选择对应替换区域或替换图像,若是要执行后续操做,请从新选择。") else: replaceImg, replaceRect = replaceObjList[-1] if replaceRect is not None: targetReplaceImg = videoOperation.adjuestImgAccordingRefImg(replaceImg, logoObjList[0][-1][0]) replaceObject = (replaceImg, replaceRect, targetReplaceImg, frame) else: print("本次操做没有选择对应替换图像,程序退出。") return print("准备工做完成,开始进行视频转换:") if ridLogoManner in [ridLogoManner_staticImg, ridLogoManner_frameImg]: ret, inf = videoOperation.convertVideo(destFName, ridLogoManner, logoObjList, replaceObject) elif ridLogoManner == ridLogoManner_inpaint: ret, inf = videoOperation.convertVideo(destFName, ridLogoManner, logoObjList, frameMask=frameMask) else: ret, inf = videoOperation.convertVideo(destFName, ridLogoManner, logoObjList, frameMask=multiFrameMask) print(inf) if __name__=='__main__': main(ridLogoManner_multiSampleInpaint)
上面的代码是以最复杂的 多Logo区域采样图像修复,能够给main函数传其余参数执行其余消除方式。
程序执行需注意:
下面是一个屡次Logo采样进行图像修复的运行案例截图:
一、视频Logo采样案例
采样左上角的Logo,因为“抖音”二字播放时不停晃动,须要采样屡次,尽可能确保“抖音”二字在不一样位置都有采样,下面只提供了一次截图:
针对右下角的Logo信息屡次截图,下面是其中的一次截图:
二、处理后的视频截图
能够看到两个角落的Logo都消除了。
在本节基础上,老猿使用PyQt开发了一个视频Logo消除的图形化界面工具,具体开发过程请见《Python音视频:开发消除抖音短视频Logo的图形化工具过程详解》。
更多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。
若是有兴趣也愿意支持老猿的读者,欢迎购买付费专栏。