对于带Logo(如抖音Logo、电视台标)的视频,有三种方案进行Logo消除:
其中:
方法1又可以使用三种方法,一是使用某固定图像替换、二是截取视频某帧的一部分图像替换、三是用每帧固定区域的图像替换当前帧的Logo区域,其中固定图像替换最简单,下面就不展开介绍;截取视频某帧的一部分图像比较简单,用每帧固定区域的图像替换当前帧的Logo区域最复杂;
方法2可以认为是方法3的特例,即填充值来源于简单计算,如Logo区域像素的均值等,我们在此不进行介绍。
方法3是以Logo去除后根据原Logo区域附近的图像像素对Logo区域进行插值填充,以确保填充后的图像整体比较协调、完整。
二、需要解决的问题可以通过cv2.imshow(winname, img)来显示一个图片,当读取视频文件的帧图片连续显示时就是一个无声的视频播放。其中的参数winname为一个英文字符串,显示为窗口的标题,OpenCV将其作为窗口的名字,作为识别窗口的标识,相同名字的窗口就是同一个窗口。
对于相关窗口,OpenCV提供鼠标及键盘事件处理机制。
3.2、OpenCV-Python的鼠标事件捕获OpenCV提供了设置鼠标事件回调函数来提供鼠标事件处理的机制,设置回调函数的方法如下:
cv2.setMouseCallback(winName, OnMouseFunction, param)
其中winName为要设置鼠标回调处理的窗口名,OnMouseFunction为回调函数,用于处理鼠标响应,param为设置回调函数时传入的应用相关特定参数,可以不设置,但需要在回调函数访问设置回调函数对象属性时非常有用。
OpenCV提供了在图像中绘制几何图形的方法,绘制的图像包括矩形、椭圆、扇形、弧等。本文主要介绍矩形的绘制,具体调用语法如下:
rectangle(img, pt1, pt2, color, thickness=None, lineType=None, shift=None)
1
其中参数:
另外该方法还有个变种调用方式:
rectangle(img, rec, color[, thickness[, lineType[, shift]]]),其中的rec为上面pt1和pt2构建的矩形。
fl_image方法为moviepy音视频剪辑库提供的视频剪辑类VideoClip的视频变换方法,具体请参考《moviepy音视频剪辑:视频剪辑基类VideoClip的属性及方法详解》。
3.5、Python的全局变量传值在python中可以使用全局变量,关于全局变量的使用请参考《 Python函数中的变量及作用域》的介绍。
3.6、OpenCV的图像修复方法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)
1
补充说明:
关于膨胀处理的知识解释有点复杂,请参考《OpenCV-Python学习—形态学处理》以及《Opencv python 锚点anchor位置及borderValue的改变对膨胀腐蚀的影响》。
图像的膨胀处理会使得图像中较亮的区域增大,较暗的区域减小。
本部分介绍的内容对Logo去除采用了如下四种方式:
以上四种处理方式,对应的消除Logo方法类型分别为:
ridLogoManner_staticImg = 1
ridLogoManner_frameImg = 2
ridLogoManner_inpaint = 3
ridLogoManner_multiSampleInpaint = 4
1234
4.1、实现思路
为了实现Logo标记的消除,具体步骤如下:
这是一个比较通用的鼠标回调函数,代码如下:
def OnMouseEvent( event, x, y, flags, param):
try:
mouseEvent = param
mouseEvent.processMouseEvent(event, x, y, flags)
except Exception as e:
print("使用回调函数OnMouseEvent的方法错误,所有使用该回调函数处理鼠标事件的对象,必须满足如下条件:")
print(" 1、必须将自身通过param传入")
print(" 2、必须定义一个processMouseEvent(self)方法来处理鼠标事件")
print(e)
12345678910
所有使用该回调函数处理鼠标事件的对象,必须将自身通过param传入到回调函数中,并且必须定义一个processMouseEvent(self)方法来处理鼠标事件。下面介绍的类CImgMouseEvent就是满足条件的类。
4.3、视频图像展现窗口的鼠标事件处理类为了支持在视频图像中进行相关操作,需要比较方便的支持并识别鼠标操作的类,在此称为CImgMouseEvent, CImgMouseEvent用于OpenCV显示图像的窗口的鼠标事件处理,会记录下前一次鼠标左键按下或释放的位置以及操作类型,并记录下当前鼠标移动、左键按下或释放事件的信息。
4.3.1、CImgMouseEvent关键属性以上鼠标事件属性的记录处理都在CImgMouseEvent的方法processMouseEvent中,但processMouseEvent方法仅记录鼠标事件属性,记录后调用父对象的 parent的processMouseEvent方法实现真正的操作
4.3.2、CImgMouseEvent主要方法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()
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
4.4、定义视频图像处理类
CSubVideoImg类用于操作视频及视频的图像,主要用于对一个视频的帧图像进行操作。
4.4.1、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掩码等,这些数据需要在操作视频图像时记录并在视频处理时传递给上述方法。
4.5、视频图像处理函数上面视频图像处理类中使用了processImg函数,该函数用于视频生成的帧图像处理函数,用静态图像或同帧区域范围图像替换,或使用图像修复术修复。
在processImg函数中,使用了全局变量来传递该函数调用时的CSubVideoImg类对象及Logo消除的方式。具体实现就二十行代码,大家可以参考视频变换的介绍自己去实现,在此就不提供了,否则就和付费专栏文章完全一样了。
4.6、主程序主程序根据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函数传其他参数执行其他消除方式。
4.7、注意程序执行需注意:
下面是一个多次Logo采样进行图像修复的运行案例截图:
1、视频Logo采样案例
采样左上角的Logo,由于“抖音”二字播放时不停晃动,需要采样多次,尽量确保“抖音”二字在不同位置都有采样,下面只提供了一次截图:
针对右下角的Logo信息多次截图,下面是其中的一次截图:
2、处理后的视频截图
可以看到两个角落的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。
如果有兴趣也愿意支持老猿的读者,欢迎购买付费专栏。
请点击下方的“了解更多”阅读原文。
,Copyright © 2008-2022 秒下下载站
m.down10s.com .All Rights Reserved