使用PYQT5打开海康威视工业相机并获取图像进行显示

目录

0 前言

1 UI界面的布局

2 UI界面布局对应的代码

3 打开海康威视工业相机等功能的完整代码

4 一些代码函数和注意事项

5 界面操作步骤和最终效果图


0 前言

       因为这段时间我主要在学习图像特征提取和机器学习,但有一些实验结果还没有出来,不能进行下去,同时也遇到了点瓶颈,不知该从哪方面继续进行下去或者如何做到创新;就简单地学习了PYQT5上位机界面可视化的内容并简单实现了一些功能(感觉也挺好玩的),后面也是需要设计一个可视化界面。就通过这时间学习了PYQT5知识。刚开始问同学这部分该如何学习,他们跟我说从网上找一些别人的demo,然后进行更改,一两天就可以学会了;我一开始还不太相信,就尝试了一下,结果发现真不难,通过学习简单实现了用PYQT5打开了电脑摄像头等功能,就有了我上两篇博客,具体也可以参考一下。

       由于我们项目使用的是海康威视工业相机,在学习PYQT5的时候我只打开了电脑自带的免驱摄像机。使用PYQT5打开海康威视相机的教程网上比较少,而且一些教程我尝试后也打不开相机,在这里就没有之前那么顺利了,花了两三天摸索。  通过几天的努力,终于实现了使用PYQT5打开海康威视工业相机。

1 UI界面的布局

下图是简单设计的一个UI控制的输出可视化界面。

     其中,<摄像头><图片显示>使用的是左边功能的 label 类、<获取相机信息><打开摄像头><拍照><关闭摄像头>使用的是左边功能的 Push Button 类。这些输出显示和按键功能中的字体大小和边框都可以对应根据右边的属性进行更改配置。

2 UI界面布局对应的代码

(1)项目的目录如下,open_camera.ui 是上面保存的的界面布局。

      其中,MvImport 文件 是在安装海康威视时候开放给我们进行二次开发的,python中支持的接口以及参数可以从相应文件中查取,可以在你安装路径中找到(…\MVS\Development\Samples\python\MvImport ),然后将 MvImport文件 复制到你的代码中。海康威视相机官方软件下载链接海康机器人-机器视觉-下载中心

(2)UI界面布局的对应代码,则是 open_camera.py 文件,可以右击 open_camera.ui 文件,找到 Exernal Tools 。然后点击 PyUIC ,就可以自动生成 open_camera.py 文件,具体操作和生成的代码如下:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'open_camera.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(970, 858)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(30, 10, 571, 391))
        font = QtGui.QFont()
        font.setPointSize(46)
        font.setBold(True)
        font.setItalic(True)
        font.setWeight(75)
        self.label.setFont(font)
        self.label.setFrameShape(QtWidgets.QFrame.Box)
        self.label.setFrameShadow(QtWidgets.QFrame.Raised)
        self.label.setLineWidth(3)
        self.label.setAlignment(QtCore.Qt.AlignCenter)
        self.label.setObjectName("label")
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(30, 410, 571, 391))
        font = QtGui.QFont()
        font.setPointSize(46)
        font.setBold(True)
        font.setItalic(True)
        font.setWeight(75)
        self.label_2.setFont(font)
        self.label_2.setFrameShape(QtWidgets.QFrame.Box)
        self.label_2.setFrameShadow(QtWidgets.QFrame.Raised)
        self.label_2.setLineWidth(3)
        self.label_2.setAlignment(QtCore.Qt.AlignCenter)
        self.label_2.setObjectName("label_2")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(700, 110, 201, 81))
        font = QtGui.QFont()
        font.setPointSize(20)
        font.setBold(True)
        font.setWeight(75)
        self.pushButton.setFont(font)
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_2.setGeometry(QtCore.QRect(700, 240, 201, 81))
        font = QtGui.QFont()
        font.setPointSize(20)
        font.setBold(True)
        font.setWeight(75)
        self.pushButton_2.setFont(font)
        self.pushButton_2.setObjectName("pushButton_2")
        self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_3.setGeometry(QtCore.QRect(700, 370, 201, 81))
        font = QtGui.QFont()
        font.setPointSize(20)
        font.setBold(True)
        font.setWeight(75)
        self.pushButton_3.setFont(font)
        self.pushButton_3.setObjectName("pushButton_3")
        self.pushButton_4 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_4.setGeometry(QtCore.QRect(700, 500, 201, 81))
        font = QtGui.QFont()
        font.setPointSize(20)
        font.setBold(True)
        font.setWeight(75)
        self.pushButton_4.setFont(font)
        self.pushButton_4.setObjectName("pushButton_4")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 970, 22))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.label.setText(_translate("MainWindow", "摄像头"))
        self.label_2.setText(_translate("MainWindow", "图片显示"))
        self.pushButton.setText(_translate("MainWindow", "获取相机信息"))
        self.pushButton_2.setText(_translate("MainWindow", "打开摄像机"))
        self.pushButton_3.setText(_translate("MainWindow", "拍照"))
        self.pushButton_4.setText(_translate("MainWindow", "关闭摄像机"))

3 打开海康威视工业相机等功能的完整代码

   下面是打开海康威视摄像头和进行拍照的完整代码 open_camera_main.py ;这里面的一些打印输出的代码被我给注释掉了,换成系统的提示、警告代码。

import cv2
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtGui import *
import numpy as np
import matplotlib.pyplot as plt
# from MvImport.MvCameraControl_header import MV_CC_DEVICE_INFO_LIST
from open_camera import Ui_MainWindow  # 导入创建的GUI类
import sys
import threading
import msvcrt
from ctypes import *
from PyQt5.Qt import *
from PIL import Image,ImageTk

sys.path.append("../MvImport")
from MvCameraControl_class import *
import time
import os

class mywindow(QtWidgets.QMainWindow, Ui_MainWindow):

    sendAddDeviceName = pyqtSignal()  # 定义一个添加设备列表的信号。
    deviceList = MV_CC_DEVICE_INFO_LIST()
    tlayerType = MV_GIGE_DEVICE | MV_USB_DEVICE

    g_bExit = False
    camera_information = False  #获取相机标志
    opencamera_flay = False #打开相机标志
    # ch:创建相机实例 | en:Creat Camera Object
    cam = MvCamera()

    def __init__(self):
        super(mywindow, self).__init__()
        self.setupUi(self)
        # self.connect_and_emit_sendAddDeviceName()
        self.init()
        self.label.setScaledContents(True)  # 图片自适应
        self.label_2.setScaledContents(True)  # 图片自适应

    def init(self):
        #获取相机相信
        self.pushButton.clicked.connect(self.get_camera_information)
        # 打开摄像头
        self.pushButton_2.clicked.connect(self.openCamera)
        # 拍照
        self.pushButton_3.clicked.connect(self.taking_pictures)
        # 关闭摄像头
        self.pushButton_4.clicked.connect(self.closeCamera)
        # Connect the sendAddDeviceName signal to a slot.
        # self.sendAddDeviceName.connect(self.camera_information)
        # Emit the signal.
        # self.sendAddDeviceName.emit()

        # 获得所有相机的列表存入cmbSelectDevice中
    def get_camera_information(self):
        '''选择所有能用的相机到列表中,
             gige相机需要配合 sdk 得到。
        '''
        # 得到相机列表
        # tlayerType = MV_GIGE_DEVICE | MV_USB_DEVICE
        # ch:枚举设备 | en:Enum device
        ret = MvCamera.MV_CC_EnumDevices(self.tlayerType, self.deviceList)
        if ret != 0:
            print("enum devices fail! ret[0x%x]" % ret)
            # QMessageBox.critical(self, '错误', '读取设备驱动失败!')
            # sys.exit()
        if self.deviceList.nDeviceNum == 0:
            QMessageBox.critical(self, "错误", "没有发现设备 ! ")
            # print("find no device!")
            # sys.exit()
        else:
            QMessageBox.information(self, "提示", "发现了 %d 个设备 !" % self.deviceList.nDeviceNum)
        # print("Find %d devices!" % self.deviceList.nDeviceNum)
        if self.deviceList.nDeviceNum == 0:
            return None

        for i in range(0, self.deviceList.nDeviceNum):
            mvcc_dev_info = cast(self.deviceList.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO)).contents
            if mvcc_dev_info.nTLayerType == MV_GIGE_DEVICE:
                print("\ngige device: [%d]" % i)
                strModeName = ""
                for per in mvcc_dev_info.SpecialInfo.stGigEInfo.chModelName:
                    strModeName = strModeName + chr(per)
                print("device model name: %s" % strModeName)
                

        self.camera_information = True
    # 打开摄像头。
    def openCamera(self, camid=0):
        if self.camera_information == True:
            self.g_bExit = False
            # ch:选择设备并创建句柄 | en:Select device and create handle
            stDeviceList = cast(self.deviceList.pDeviceInfo[int(0)], POINTER(MV_CC_DEVICE_INFO)).contents
            ret = self.cam.MV_CC_CreateHandle(stDeviceList)
            if ret != 0:
                # print("create handle fail! ret[0x%x]" % ret)
                QMessageBox.critical(self, "错误", "创建句柄失败 ! ret[0x%x]" % ret)
                # sys.exit()
            # ch:打开设备 | en:Open device
            ret = self.cam.MV_CC_OpenDevice(MV_ACCESS_Exclusive, 0)
            if ret != 0:
                # print("open device fail! ret[0x%x]" % ret)
                QMessageBox.critical(self, "错误", "打开设备失败 ! ret[0x%x]" % ret)
                # sys.exit()
            # ch:探测网络最佳包大小(只对GigE相机有效) | en:Detection network optimal package size(It only works for the GigE camera)
            if stDeviceList.nTLayerType == MV_GIGE_DEVICE:
                nPacketSize = self.cam.MV_CC_GetOptimalPacketSize()
                if int(nPacketSize) > 0:
                    ret = self.cam.MV_CC_SetIntValue("GevSCPSPacketSize", nPacketSize)
                    if ret != 0:
                        # print("Warning: Set Packet Size fail! ret[0x%x]" % ret)
                        QMessageBox.warning(self, "警告", "报文大小设置失败 ! ret[0x%x]" % ret)
                else:
                    # print("Warning: Get Packet Size fail! ret[0x%x]" % nPacketSize)
                    QMessageBox.warning(self, "警告", "报文大小获取失败 ! ret[0x%x]" % nPacketSize)

            # ch:设置触发模式为off | en:Set trigger mode as off
            ret = self.cam.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_OFF)
            if ret != 0:
                # print("set trigger mode fail! ret[0x%x]" % ret)
                QMessageBox.critical(self, "错误", "设置触发模式失败 ! ret[0x%x]" % ret)
                # sys.exit()
                # ch:获取数据包大小 | en:Get payload size
            stParam = MVCC_INTVALUE()
            memset(byref(stParam), 0, sizeof(MVCC_INTVALUE))

            ret = self.cam.MV_CC_GetIntValue("PayloadSize", stParam)
            if ret != 0:
                # print("get payload size fail! ret[0x%x]" % ret)
                QMessageBox.critical(self, "错误", "获取有效负载大小失败 ! ret[0x%x]" % ret)
                # sys.exit()
            nPayloadSize = stParam.nCurValue

            # ch:开始取流 | en:Start grab image
            ret = self.cam.MV_CC_StartGrabbing()
            if ret != 0:
                # print("start grabbing fail! ret[0x%x]" % ret)
                QMessageBox.critical(self, "错误", "开始抓取图像失败 ! ret[0x%x]" % ret)
                # sys.exit()

            data_buf = (c_ubyte * nPayloadSize)()
            self.opencamera_flay = True
            try:
                hThreadHandle = threading.Thread(target=self.work_thread, args=(self.cam, data_buf, nPayloadSize))
                hThreadHandle.start()
            except:
                # print("error: unable to start thread")
                QMessageBox.critical(self, "错误", "无法启动线程 ! ")

        else:
            QMessageBox.critical(self, '错误', '获取相机信息失败!')
            return None

    # 关闭相机
    def closeCamera(self):
        if self.opencamera_flay == True:
            self.g_bExit = True
            # ch:停止取流 | en:Stop grab image
            ret = self.cam.MV_CC_StopGrabbing()
            if ret != 0:
                # print("stop grabbing fail! ret[0x%x]" % ret)
                QMessageBox.critical(self, "错误", "停止抓取图像失败 ! ret[0x%x]" % ret)
                # sys.exit()

            # ch:关闭设备 | Close device
            ret = self.cam.MV_CC_CloseDevice()
            if ret != 0:
                # print("close deivce fail! ret[0x%x]" % ret)
                QMessageBox.critical(self, "错误", "停止设备失败 ! ret[0x%x]" % ret)
            # ch:销毁句柄 | Destroy handle
            ret = self.cam.MV_CC_DestroyHandle()
            if ret != 0:
                # print("destroy handle fail! ret[0x%x]" % ret)
                QMessageBox.critical(self, "错误", "销毁处理失败 ! ret[0x%x]" % ret)

            self.label.clear()  # 清除label组件上的图片
            self.label_2.clear()  # 清除label组件上的图片
            self.label.setText("摄像头")
            self.label_2.setText("显示图片")
            self.camera_information = False
            self.opencamera_flay = False
        else:
            QMessageBox.critical(self, '错误', '未打开摄像机!')
            return None

    def work_thread(self, cam=0, pData=0, nDataSize=0):
        stFrameInfo = MV_FRAME_OUT_INFO_EX()
        memset(byref(stFrameInfo), 0, sizeof(stFrameInfo))
        while True:
            ret = cam.MV_CC_GetOneFrameTimeout(pData, nDataSize, stFrameInfo, 1000)
            if ret == 0:
                image = np.asarray(pData)  # 将c_ubyte_Array转化成ndarray得到(3686400,)
                # print(image.shape)
                image = image.reshape((2000, 2688, 1))  # 根据自己分辨率进行转化
                # image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)  # 这一步获取到的颜色不对,因为默认是BRG,要转化成RGB,颜色才正常
                image = cv2.cvtColor(image, cv2.COLOR_BAYER_GB2BGR)  # Bayer格式(raw data)向RGB或BGR颜色空间的转换
                # print(image.shape)
                # pyrD1 = cv2.pyrDown(image)  # 向下取样
                # pyrD2 = cv2.pyrDown(pyrD1)  # 向下取样
                image_height, image_width, image_depth = image.shape  # 读取图像高宽深度
                self.image_show = QImage(image.data, image_width, image_height, image_width * image_depth,
                                         QImage.Format_RGB888)
                self.label.setPixmap(QPixmap.fromImage(self.image_show))
            if self.g_bExit == True:
                del pData
                break

    #拍照
    def taking_pictures(self):
        if self.opencamera_flay == True:
            FName = fr"images/cap{time.strftime('%Y%m%d%H%M%S', time.localtime())}"
            print(FName)
            self.label_2.setPixmap(QtGui.QPixmap.fromImage(self.image_show))
            # self.showImage.save(FName + ".jpg", "JPG", 100)
            self.image_show.save('./1.bmp')
        else:
            QMessageBox.critical(self, '错误', '摄像机未打开!')
            return None
    #重写关闭函数
    def closeEvent(self, event):
        reply = QMessageBox.question(self,'提示',"确认退出吗?",QMessageBox.Yes | QMessageBox.No,QMessageBox.No)
        if reply == QMessageBox.Yes:
            event.accept()
            #用过sys.exit(0)和sys.exit(app.exec_()),但没起效果
            os._exit(0)
        else:
            event.ignore()

if __name__ == '__main__':
    from PyQt5 import QtCore
    QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)  # 自适应分辨率

    app = QtWidgets.QApplication(sys.argv)
    window = mywindow()
    window.show()
    sys.exit(app.exec_())

4 一些代码函数和注意事项

(1)在 MvImport文件 中,最主要调用的是 MvCameraControl_class.py ,该文件中包含从C语言底层接口封装过来的所有 python 可调用接口,在调用时是需要导入该文件作为调用包;需要注意的是 sys.path.append("../MvImport") 使用的路径是相对路径,主要是用于 from MvCameraControl_class import * 这句包导入时使用,在此处可能会遇到一个问题,问题报错如下:

Traceback (most recent call last):
  File "D:/代码练习/PYQT5/PYQT5_demo_2/open_camera_main.py", line 17, in <module>
    from MvCameraControl_class import *
  File "D:\代码练习\PYQT5\PYQT5_demo_2\MvImport\MvCameraControl_class.py", line 15, in <module>
    MvCamCtrldll = WinDLL("MvCameraControl.dll")
  File "E:\anaconda\envs\pytorch\lib\ctypes\__init__.py", line 373, in __init__
    self._handle = _dlopen(self._name, mode)
FileNotFoundError: Could not find module 'MvCameraControl.dll' (or one of its dependencies). Try using the full path with constructor syntax.

    主要原因是在  MvCameraControl_class.py  文件中,使用  MvCameraControl.dll  文件时,相对路径失效导致,可以对  MvCameraControl_class.py 文件中导包部分进行如下修改即可; 

MvCamCtrldll = WinDLL("C:\\Program Files (x86)\\Common Files\\MVS\\Runtime\\Win64_x64\\MvCameraControl.dll")

(2) 红色框中的第一步,需要根据你使用相机的分辨率进行更改,注意顺序不能搞错。

在红色框的第二步,我设置后只能显示灰度图片,具体我也不清楚什么原因,然后我将其改成红色框的第三步代码,就可以实现彩色图像显示。

(3)  假如打开海康威视相机后,没有点击关闭摄像机,直接关闭窗口,会出现程序还一直在运行,没有结束,具体我也不清楚是什么原因,可能是线程问题,如下所示 :

      然后我在程序中添加了以下代码( 重写 关闭函数  ),则就可以解决以上情况,具体原因还没分析,后续可以深入了解一下。

def closeEvent(self, event):
    reply = QtWidgets.QMessageBox.question(self,'提示',"确认退出吗?",QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,QtWidgets.QMessageBox.No)
    if reply == QtWidgets.QMessageBox.Yes:
        event.accept()
        #用过sys.exit(0)和sys.exit(app.exec_()),但没起效果
        os._exit(0)
    else:
        event.ignore()

(4) 相机分辨率与PYQT5上位机显示界面图像显示自适应问题;一般由于工业相机的分辨率比较高,但我们设计的上位机的相机图片实时显示范围有限,所以会出现显示不全的结果。为了解决以上的问题,可在如下位置添加以下这两行代码,则可解决相机分辨高而图像显示不全的问题,即显示图像自适应。

 self.label.setScaledContents(True)  # 窗口显示图片自适应
 self.label_2.setScaledContents(True)  # 窗口显示图片自适应

5 界面操作步骤和最终效果图

(1)操作步骤 (这些操作逻辑可以根据自己的方式更改)

第一步:点击 <获取相机信息> 按钮,若弹出 没有发现设备 的弹窗,则获取相机信息失败,不能操作接下来步骤;

第二步:点击 <打开摄像机> 按钮,若出现一些弹窗提示或警告,则打开摄像机失败,不能进行拍照;

第三步:点击 <拍照> 按钮,只有执行以上两步并都成功了,才可以进行拍照采集图像,不然会出现 未打开摄像机 的弹窗;

第四步:操作完成后,就可以关闭摄像机。

(2)效果图:

参考来源: 

使用PYQT5打开电脑摄像头并进行拍照_暂未成功人士!的博客-CSDN博客

pyQT5 学习使用 笔记 六 pyQt5+opencv 显示海康GIGE相机动态视频流_youandme520的博客-CSDN博客_pyqt 海康相机

python调用海康工业相机并用opencv显示(整体实现)_J&A~ing的博客-CSDN博客_python调用海康工业相机

python语言下使用pyqt中的QImage对海康工业相机获取到的图像进行显示_J&A~ing的博客-CSDN博客_python qimage

来源:暂未成功人士!

物联沃分享整理
物联沃-IOTWORD物联网 » 使用PYQT5打开海康威视工业相机并获取图像进行显示

发表评论