Python opencv 播放视频详解

Python opencv

  • 内容简介
    • 使用 QTimer 定时器播放
    • 使用线程(Thread)播放
    • 网页播放
    • 逐帧播放
    • 倍速播放
    • 进度条
    • 抓拍
    • 下载
    • 局部放大
    • 使用布局后 播放视频随窗口大小的改变而变动
  • 总结

内容简介

本文使用python 语言,使用 Pyqt5+opencv 实现视频播放。包括:开始/停止播放、网页播放、逐帧播放、倍速播放、播放进度条、抓拍、下载、局部放大等。

使用 QTimer 定时器播放

核心代码

#加载视频 启动定时器  暂停播放时停止定时器即可
self.cap = cv2.VideoCapture(self.url)
self.fps=self.cap.get(5)
self.timer_camera = QTimer(self)
self.timer_camera.timeout.connect(self.show_pic)

#显示视频
def show_pic(self):
   #暂停
   if self.bfC == 5:
       return
   success, frame = self.cap.read()
   if success:
       self.img=frame
       show = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
       showImage = QImage(show.data, show.shape[1], show.shape[0], QImage.Format_RGB888)
       # showImage=showImage.copy(self.x1, self.y1,self.x2, self.y2)
       pix=QPixmap.fromImage(showImage)
       pix = pix.scaled(self.label.width(), self.label.height(), Qt.IgnoreAspectRatio)
       #--------------------------------------
       #局部放大
       pix = pix.copy(self.x1, self.y1,self.x2, self.y2)
       self.label.setPixmap(pix)
       #进度条
       self.jdt()
       cv2.waitKey(int(1000/self.fps))
   else:
       self.img = ""
       self.pushButton_2.setText("重播")
       self.timer_camera.stop()

使用线程(Thread)播放

当需要播放多个视频时 使用线程播放

	# 显示视频
    def show_pic(self,ids,rtmpUrl):
        pp = 1
        #加载多次视频【当加载视频流/网络视频时 有可能一次加载不上】
        while pp < 4:
            cap = cv2.VideoCapture(rtmpUrl)
            fps = int(cap.get(cv2.CAP_PROP_FPS))
            print("-----------------------1111111", fps)
            if fps == 0:
                pp += 1
                continue
            break
        if fps == 0:
            print("视频流获取失败=====》》",rtmpUrl)
            # QMessageBox.information(self, "提示框", "读取视频失败,请查看网络连接!", QMessageBox.Yes)
            return
        aa={"id":ids,"cap":cap}
        #将加载的视频对象存入集合中
        self.splList.append(aa)
        #播放窗口
        label = self.monitorList[ids]["monitorObject"]
        #值为1 表示该窗口正在播放视频   停止播放视频时改变改值即可
        self.monitorList[ids]["previewHandler"]=1
        # 暂停
        while True:
            if self.monitorList[ids]["previewHandler"]==1:
                try:
                    success, frame = cap.read()
                    if success:
                        show = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                        showImage = QImage(show.data, show.shape[1], show.shape[0], QImage.Format_RGB888)
                        pix = QPixmap.fromImage(showImage)
                        pix = pix.scaled(label.width(),label.height(), Qt.IgnoreAspectRatio)
                        #全屏下才可局部放大
                        if label.isFullScreen():
                            pix = pix.copy(self.x1, self.y1, self.x2, self.y2)
                        label.setPixmap(pix)
                        cv2.waitKey(1)
                    else:
                        cap.release()
                        self.splList.remove(aa)
                        self.monitorList[ids]["previewHandler"] = 0
                        print("opencv 读取失败===========>", self.splList)
                        return
                except Exception as e:
                    cap.release()
                    self.splList.remove(aa)
                    self.monitorList[ids]["previewHandler"] = 0
                    print("读取cap异常======》》",e)
                    return
            else:
                cap.release()
                self.splList.remove(aa)
                self.monitorList[ids]["previewHandler"] = 0
                print("停止播放======》》")
                return

网页播放

#启动 python 服务
os.system("python -m http.server 8083")
#打开html
webbrowser.open_new("http://localhost:8081/SSBF.html?sl=l&url=%s" % (self.url))
                                           

其中包含了播放本地视频以及摄像机视频。选用object标签 浏览器支持IE11


<html>
<head> 
<meta charset="utf-8"> 
<title>Object 播放视频title> 
     <script type="text/javascript" src="jquery-3.2.1.min.js">script>
head>


<body id="dsb">








body>

<script type="text/javascript">

    function jscs() {

        // alert(0);
        var bb = "";
        var bb2 = "";
        bb += "";
        bb2 += "";
        $("#dsb").html(bb);
        // $("#pp").oncanplay;

    }



     function aa() {
         // alert("------------------jhjkhjk");
         //  alert($("#pp").height());
          $("#pp").width(800);
          $("#pp").height(800);
          // alert($("#pp").height());

     }


     function load() {
        var url = document.location.toString();//获取url地址
        var urlParmStr = url.slice(url.indexOf('?')+1);//获取问号后所有的字符串
        var arr = urlParmStr.split('&');//通过&符号将字符串分割转成数组
        var sl=arr[0].split("=")[1];
        if (sl=="s"){
            var ip=arr[1].split("=")[1];
            var account=arr[2].split("=")[1];
            var pasword=arr[3].split("=")[1];
            var brand=arr[4].split("=")[1];
        }
        else if(sl=="l"){
            var url=arr[1].split("=")[1];
        }

        // alert(ip);
        // alert(account);
        // alert(pasword);
        // alert(brand);
        var html=""
        if (sl=="s"){
            if (brand == "h"){
                html=html+"+ account +":"+pasword+"@"+ip+":554/h265/ch1/sub/av_stream\" type='application/x-vlc-plugin' pluginspage=\"http://www.videolan.org/\" events='false'  id='pp' classid='clsid:9BE31822-FDAD-461B-AD51-BE1D1C159921' codebase=\"http://downloads.videolan.org/pub/videolan/vlc/latest/win32/axvlc.cab\">"

            }
            else if (brand == "d") {
                html=html+"+ account +":"+pasword+"@"+ip+":554/cam/realmonitor?channel=1&subtype=0\" type='application/x-vlc-plugin' pluginspage=\"http://www.videolan.org/\" events='false'  id='pp' classid='clsid:9BE31822-FDAD-461B-AD51-BE1D1C159921' codebase=\"http://downloads.videolan.org/pub/videolan/vlc/latest/win32/axvlc.cab\">"
            }
        }
        else if(sl =="l"){
            html=html+""
        }
        $("#dsb").html(html)
    }


    window.onload=function(){
        // alert("onload");
        // jscs();
        load();
        aa();
    }
script>
html>

逐帧播放

实现简单 就是获取当前帧 加1 或减1。self.pos的值详见进度条

	#逐帧播放
    def zzbfShow(self):
        try:
            success, frame = self.cap.read()
            if success:
                show = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                showImage = QImage(show.data, show.shape[1], show.shape[0], QImage.Format_RGB888)
                # 图片自适应控件尺寸
                self.label.setScaledContents(True)
                self.label.setPixmap(QPixmap.fromImage(showImage))
                self.jdt()
            else:
                self.pushButton_2.setText("重播")
                self.timer_camera.stop()
        except Exception as e:
            print("eeeee==============>",e)
	#上一帧
    def syz(self):
        self.pos -=1
        self.horizontalSlider.setValue(self.pos)
        self.horizontalSlider.value()
        self.cap.set(1,self.pos)
        self.zzbfShow()
	#下一帧
    def xyz(self):
        self.pos += 1
        self.horizontalSlider.setValue(self.pos)
        self.horizontalSlider.value()
        self.cap.set(1, self.pos)
        self.zzbfShow()

倍速播放

通过改变 self.fps 的值,使得在播放方法中 cv2.waitKey(int(1000/self.fps)) 等待时间改变控制播放速度。

	#倍速播放
    def bsbf(self):
        name = self.comboBox.currentText()
        if name == "正常":
            self.fps=self.cap.get(5)
        elif name == "2倍":
            self.fps=self.cap.get(5)*2
        elif name == "0.5倍":
            self.fps = int(self.cap.get(5) / 2)
        else:
            pass

进度条

自定义进度条,重写点击事件。否则 Pyqt5 进度条在单击时 会在进度条上前后移动,无法实现点那里就播放那里的视频。

from PyQt5.QtWidgets import QSlider
from PyQt5.QtCore import Qt,pyqtSignal


class MySlide(QSlider):
    clicked = pyqtSignal(object)
    def __init__(self, parent=None):
        super(MySlide, self).__init__(parent)


    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
        # if event.button() == Qt.RightButton:
            super().mousePressEvent(event)  # 调用父级的单击事件,听说这样能不影响进度条原来的拖动
            val_por = event.pos().x() / self.width()  # 获取鼠标在进度条的相对位置
            self.setValue(int(val_por * self.maximum()))  # 改变进度条的值
            self.clicked.emit(self.value())	# 点击发送信号,这里可不要

        # self.clicked.emit(self.rect)

#进度条拖拽事件
self.horizontalSlider.sliderMoved.connect(self.movedSlide)
#进度条释放事件
self.horizontalSlider.sliderReleased.connect(self.releasedSlide)
#进度条点击事件
self.horizontalSlider.clicked.connect(self.onClickSlide)
#设置进度条的值 self.count为视频总帧数
self.count=self.cap.get(7)
self.horizontalSlider.setMinimum(0)
self.horizontalSlider.setMaximum(self.count)

	# 单击滑块
    def onClickSlide(self):
        self.pos = self.horizontalSlider.value()
        self.cap.set(1, self.pos)

    # 拖动滑块
    def movedSlide(self):
        self.bfC=5

    # 释放滑块
    def releasedSlide(self):
        self.bfC=2
        self.pos = self.horizontalSlider.value()
        self.cap.set(1, self.pos)
	
	#进度条
    def jdt(self):
        if self.bfC == 5:
            return
        d=int(self.pos/self.fps)
        a = datetime.timedelta(seconds=d)
        b = str(a).split(":")
        c = b[1] + ":" + b[2]
        self.label_2.setText(c)
        # 设置进度条的值
        if self.loop_flag == self.pos:
            self.loop_flag = self.loop_flag + 2
            self.horizontalSlider.setValue(self.loop_flag)
        else:
            self.pos = self.horizontalSlider.value()
            self.loop_flag = self.pos

抓拍

	#抓拍
    def zp(self):
        stratTime = datetime.datetime.now().strftime('%Y-%m-%d%H%M%S')
        path = "D://DCXT//img//"
        try:
            if self.img !="":
                path+=stratTime+".jpg"
                cv2.imwrite(path, self.img)
                QMessageBox.information(self, "提示框", "抓拍成功", QMessageBox.Yes)
        except Exception as e:
            print("抓拍异常=======>",e)
            QMessageBox.information(self, "提示框", "抓拍失败", QMessageBox.Yes)

下载

#线程下载视频
class downloadThread(QThread):
    add_item = pyqtSignal(str)
    show_time = pyqtSignal(str)

    def __init__(self, surl,url,*args, **kwargs):
        super(downloadThread, self).__init__(*args, **kwargs)
        print('视频下载地址surl==',surl)
        print('视频地址url==',url)
        self.surl = surl
        self.vurl=url
        self.needStop = False

    def run(self, *args, **kwargs):
        self.savefile(self.surl,self.vurl)

    # 文件下载 视频下载地址、视频地址
    def savefile(self,saveflieurl,vurl):
        download_url =vurl
        #视频的保存地址+名称
        path = os.path.join(saveflieurl, os.path.basename(download_url))
        print('thread => save',path)
        # 缓存下载
        try:
            #打开一个远程的url连接
            response = urllib.request.urlopen(download_url)
            filesize = response.headers['content-length']
            print('self.filesize=====', filesize)
        except Exception as e:
            print('e2====', e)
        try:
            r = requests.get(download_url, stream=True)
            f = open(path, "wb")
            offset = 0
            # chunk是指定每次写入的大小,每次只写了512byte
            for chunk in r.iter_content(chunk_size=512):
                if chunk and not self.needStop:
                    f.seek(offset)
                    f.write(chunk)
                    offset = offset + len(chunk)
                    proess = offset / int(filesize) * 100
                    print('proess=====', proess)
                    # 发送添加信号
                    self.add_item.emit(str(proess))
                    #time.sleep(0.5)
                else:
                    break
            f.close()
        except Exception as e:
            print('e=====', e)

    def stopdownload(self):
        self.needStop = True

  #下载
    def downVideo(self):
        if self.downstateAll >= 2:
            if self.pushButton.text() == '下载':
                self.pushButton.setText('取消')
                # 声明线程实例
                self.downloadthread = downloadThread(self.saveflieurl, self.url)
                # 绑定增加控件函数
                self.downloadthread.add_item.connect(self.download_progress)
                self.downloadthread.start()
            else:
                self.downloadthread.stopdownload()
                # 删除文件
                filePath = self.saveflieurl + '\\' + os.path.basename(self.url)
                if os.path.isfile(filePath):
                    print('删除文件的路径是===', filePath)
                    os.remove(filePath)
                    print('删除文件成功')
                else:
                    pass
                self.downloadthread.quit()
                self.downloadthread.wait()
                self.pushButton.setText('下载')
        else:
            QMessageBox.information(self, "提示", "没有权限下载!", QMessageBox.Yes)
	
	#下载进度控件
    def download_progress(self,progress):
        self.progressBar.setValue(float(progress))
        if((float(progress))>=100):
            QMessageBox.information(self, '信息', '下载成功!')
            self.pushButton.setText('下载')
        else:
            pass

局部放大

重写 QLabel 实现可在label上绘画出要局部放大的区域,将坐标返回。坐标返回后要乘以 根据视频的分辨率以及播放窗口的比例值。
目前这种实现局部放大的方法就是复制所选矩形区域的图片 放到label 中,自适应显示。
另一种就是将视频图片放大一定的倍数,然后在偏移到自己的播放控件label中。
如果你有更好的方法可以评论区讲下【虚心求教】。

# 图片自适应控件尺寸
self.label.setScaledContents(True)

from PyQt5.QtCore import Qt,QRect,pyqtSignal
from PyQt5.QtWidgets import QLabel
from PyQt5.QtGui import QPainter,QPen,QMouseEvent
# from videoPlayTest import MyVideoPlay

class MyLabel(QLabel):
    clicked = pyqtSignal(object)
    doubleClick=pyqtSignal(str)
    # sendmsg.connect(MyVideoPlay.MySlot)

    def __init__(self, parent=None):
        super(MyLabel, self).__init__(parent)
        self.x0 = 0
        self.y0 = 0
        self.x1 = 0
        self.y1 = 0
        self.pos0 = None
        self.pos1 = None
        self.flag = False
        self.isShow=False

        self.rect=""


    #绘制矩形
    def paintEvent(self, event):
        super().paintEvent(event)
        self.rect = QRect(self.x0, self.y0, self.x1 - self.x0, self.y1 - self.y0)
        painter = QPainter(self)
        painter.setPen(QPen(Qt.red, 2, Qt.SolidLine))
        if self.isShow == True:
            painter.drawRect(self.rect)
            # print("绘制矩形=========>",self.rect)

    #获取起始点
    def mousePressEvent(self, event):
        QLabel.mousePressEvent(self, event)
        self.flag = True
        self.pos0 = event.globalPos()
        self.x0 = event.x()
        self.y0 = event.y()
        # print("获取起始点======>",self.x0,self.y0)

    #鼠标移动
    def mouseMoveEvent(self, event):
        QLabel.mouseMoveEvent(self, event)
        if self.flag:
            self.pos1 = event.globalPos()
            self.x1 = event.x()
            self.y1 = event.y()
            self.update()
            self.isShow = True
            # print("鼠标移动=====>",self.x1, self.y1)

    #鼠标释放
    def mouseReleaseEvent(self, event):
        QLabel.mouseReleaseEvent(self, event)
        self.x0 = 0
        self.y0 = 0
        self.x1 = 0
        self.y1 = 0
        self.pos0 = None
        self.pos1 = None
        self.flag = False
        self.isShow = False
        print("鼠标释放=====>",self.rect)
        self.clicked.emit(self.rect)

    # 返回绝对坐标
    def getRectGlobalPos(self):
        poses = [(self.pos0.x(), self.pos0.y())]
        poses.append((self.pos1.x(), self.pos1.y()))
        print("返回绝对坐标=====>",poses)
        return poses

    def mouseDoubleClickEvent(self,QMouseEvent):
        self.doubleClick.emit("double clicked")

	#接收局部放大信号
    def MySlot(self,rect):
        print("接收局部放大信号=====>",rect,rect.x(),rect.y())
        if self.label.isFullScreen():
            self.x1 = rect.x()
            self.y1 = rect.y() 
            self.x2 = rect.width()
            self.y2 = rect.height()
        else:
            self.x1=rect.x()*0.78
            self.y1=rect.y()*0.78
            self.x2=rect.width()*0.78
            self.y2=rect.height()*0.78

使用布局后 播放视频随窗口大小的改变而变动

视频播放窗口可以放大,但变小时缩不回来。这个问题让我找了一两天,,真是。。。。。。。

有两点:
1.使用布局不可以默认 Stretch 为0。应这样使用。

	self.gridLayout_4.setColumnStretch(0, 1)
    self.gridLayout_4.setColumnStretch(1, 1)
    self.gridLayout_4.setColumnStretch(2, 1)
    self.gridLayout_4.setColumnStretch(3, 1)
    self.gridLayout_4.setRowStretch(0, 1)
    self.gridLayout_4.setRowStretch(1, 1)
    self.gridLayout_4.setRowStretch(2, 1)
    self.gridLayout_4.setRowStretch(3, 1)

在用到单多画面切换时【比如我用的4、9、16画面切换】

    def show4(self):
        self.hm = 4
        self.gridLayout_4.setColumnStretch(0, 1)
        self.gridLayout_4.setColumnStretch(1, 1)
        self.gridLayout_4.setColumnStretch(2, 0)
        self.gridLayout_4.setColumnStretch(3, 0)
        self.gridLayout_4.setRowStretch(0, 1)
        self.gridLayout_4.setRowStretch(1, 1)
        self.gridLayout_4.setRowStretch(2, 0)
        self.gridLayout_4.setRowStretch(3, 0)
        self.selectedMonitor = -1
        self.fubin2.append(0)
        self.fubin2.append(0)
        self.DispLb_1.show()
        self.DispLb_2.show()
        self.DispLb_3.hide()
        self.DispLb_4.hide()
        self.DispLb_5.show()
        self.DispLb_6.show()
        self.DispLb_7.hide()
        self.DispLb_8.hide()
        self.DispLb_9.hide()
        self.DispLb_10.hide()
        self.DispLb_11.hide()
        self.DispLb_12.hide()
        self.DispLb_13.hide()
        self.DispLb_14.hide()
        self.DispLb_15.hide()
        self.DispLb_16.hide()
        
    def show9(self):
        self.hm = 9
        self.selectedMonitor = -1
        self.fubin2.append(0)
        self.fubin2.append(0)
        self.gridLayout_4.setColumnStretch(0, 1)
        self.gridLayout_4.setColumnStretch(1, 1)
        self.gridLayout_4.setColumnStretch(2, 1)
        self.gridLayout_4.setColumnStretch(3, 0)
        self.gridLayout_4.setRowStretch(0, 1)
        self.gridLayout_4.setRowStretch(1, 1)
        self.gridLayout_4.setRowStretch(2, 1)
        self.gridLayout_4.setRowStretch(3, 0)
        self.DispLb_1.show()
        self.DispLb_2.show()
        self.DispLb_3.show()
        self.DispLb_4.hide()
        self.DispLb_5.show()
        self.DispLb_6.show()
        self.DispLb_7.show()
        self.DispLb_8.hide()
        self.DispLb_9.show()
        self.DispLb_10.show()
        self.DispLb_11.show()
        self.DispLb_12.hide()
        self.DispLb_13.hide()
        self.DispLb_14.hide()
        self.DispLb_15.hide()
        self.DispLb_16.hide()

    def show16(self):
        self.hm = 16
        self.gridLayout_4.setColumnStretch(0, 1)
        self.gridLayout_4.setColumnStretch(1, 1)
        self.gridLayout_4.setColumnStretch(2, 1)
        self.gridLayout_4.setColumnStretch(3, 1)
        self.gridLayout_4.setRowStretch(0, 1)
        self.gridLayout_4.setRowStretch(1, 1)
        self.gridLayout_4.setRowStretch(2, 1)
        self.gridLayout_4.setRowStretch(3, 1)
        self.selectedMonitor = -1
        self.fubin2.append(0)
        self.fubin2.append(0)
        self.DispLb_1.show()
        self.DispLb_2.show()
        self.DispLb_3.show()
        self.DispLb_4.show()
        self.DispLb_5.show()
        self.DispLb_6.show()
        self.DispLb_7.show()
        self.DispLb_8.show()
        self.DispLb_9.show()
        self.DispLb_10.show()
        self.DispLb_11.show()
        self.DispLb_12.show()
        self.DispLb_13.show()
        self.DispLb_14.show()
        self.DispLb_15.show()
        self.DispLb_16.show()

2.必须要给控件设置最小值。【不知道为什么,有懂的大神请赐教】

self.label.setMinimumSize(QtCore.QSize(10, 10))

总结

希望能对你有帮助。
欢迎指教,共同进步!!!

你可能感兴趣的:(python,opencv,pyqt5,cv)