环境:python3.6

PyQt 5.11.3

cython 0.28.7

guiqwt 3.0.3

opencv-python 3.4.3

项目要求是软件通过CCD传感器采集激光并分析数据。

现在开发阶段没有硬件设备,用手机连接到电脑上代替摄像头。手机上用的DroidCamX,电脑上用的Droidcam Client。

首先采集摄像头的图像并转换成灰度图,然后转换成彩虹图。

PyQt分线程采集图像数据并转换成灰度图,然后将灰度图数据发送到主线程。

#device.py

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

import cv2,time
from PyQt5.QtCore import pyqtSignal,QThread
import numpy as np

class ccd_dev(QThread):
    datasignal = pyqtSignal(int,list)
    def __init__(self):
        super(ccd_dev, self).__init__(parent=None)

        self.stop = 0

    def run(self):
        cap = cv2.VideoCapture(0)

        while 1:
            #print('start')
            time.sleep(0.001)
            if cap.isOpened():
                ret,frame = cap.read()
                if ret:
                    clr = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
                    self.datasignal.emit(1,list(clr))

                if self.stop:
                    cap.release()
                    break

    def set_stop(self):
        self.stop = 1

需要将灰度图转换成彩虹图,用matplotlib的cmap刷新太慢,所以要使用自制彩虹图算法。

参照了这个

但是图像数据大小是1280*640,python性能有限速度很慢,大概在0.7秒左右才能计算一次,所以采用Cython。

参照网上的碎片信息,试着做了一下。calxd是将灰度图转换成彩虹图的算法,calxm是找出各行最大值组成一维数组,各列最大值组成一维数组。

#calc.pyx

import numpy as np
cimport numpy as np
cimport cython
DTYPE = np.uint8
DTYPE1 = np.int

ctypedef np.uint8_t DTYPE_t
ctypedef np.int_t DTYPE1_t

@cython.boundscheck(False)
@cython.wraparound(False)
def calxd(np.ndarray[DTYPE_t,ndim=2] data):
    cdef int height, width ,i ,j
    cdef unsigned char num, r, g, b#0-255
    height = data.shape[0]
    width = data.shape[1]
    cdef np.ndarray[DTYPE_t, ndim=3] rgb = np.zeros([height,width,3],dtype=DTYPE)#3维,全为零的数组

    for i in range(height):
        for j in range(width):
            num = data[i,j]
            if num <=25:#在彩虹图算法的基础上增加了灰色
                b = num * 1
                g = num * 1
                r = num * 1
            elif num <= 51:
                num -= 25
                b = 255
                g = num * 5
                r = 0
            elif num <= 102:
                num -= 51
                b = 255 - num * 5
                g = 255
                r = 0
            elif num <= 153:
                num -= 102
                b = 0
                g = 255
                r = num * 5
            elif num <= 204:
                num -= 153
                b = 0
                g = 255 - <unsigned char>(128 * num / 51 + 0.5)#类型转换
                r = 255
            else:
                num -= 204
                b = 0
                g = 127 - <unsigned char>(127 * num / 51 + 0.5)
                r = 255

            #rgb[i,j] = [r,g,b]#这种太慢

            rgb[i,j,0] = r
            rgb[i,j,1] = g
            rgb[i,j,2] = b

    return rgb


@cython.boundscheck(False)
@cython.wraparound(False)
def calxm(np.ndarray[DTYPE_t,ndim=2] data):
    cdef int i, j, height, width, max, num
    height = data.shape[0]
    width = data.shape[1]

    cdef np.ndarray[DTYPE_t, ndim=1] ver = np.zeros([height],dtype=DTYPE)
    cdef np.ndarray[DTYPE_t, ndim=1] hor = np.zeros([width],dtype=DTYPE)
    cdef np.ndarray[DTYPE1_t, ndim=1] x = np.zeros([width],dtype=DTYPE1)
    cdef np.ndarray[DTYPE1_t, ndim=1] y = np.zeros([height],dtype=DTYPE1)
    cdef np.ndarray[DTYPE_t, ndim=2] d1 = np.transpose(data)#转置数组

    #ver = [max(dat) for dat in data]#这种超慢
    for i in range(height):
        max = data[i,0]
        y[i] = height - i
        for j in range(width):
            num = data[i,j]
            if num > max:
                max = num
        ver[i] = max

    for i in range(width):
        x[i] = i
        max = d1[i,0]
        for j in range(height):
            num = d1[i,j]
            if num > max:
                max = num
        hor[i] = max

    return ver,hor,x,y

然后创建一个setup.py文件,准备编译,因为calc.pyx文档里包含了

cimport numpy as np

所以setup文件要加入

include_dirs=[np.get_include()]

#setup.py

from distutils.core import setup
from Cython.Build import cythonize
import numpy as np
setup(
    name='calculation', ext_modules=cythonize('calc.pyx'),include_dirs=[np.get_include()])

在cmd命令行cd到当前路径,并输入python setup.py build_ext  --inplace

编译完成大概是这个样子

一张照片实现CCD相机的效果 PYTHON 用python打开ccd相机_guiqwt

编译完成文件夹会出现两个后缀名为*.c和*.pyd的文件。

一张照片实现CCD相机的效果 PYTHON 用python打开ccd相机_Cython_02

然后测试一下编译的代码运行的速度

#test13.py

import cv2
import calc
import time

img2 = cv2.imread('123.png')

img = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

height,width = img.shape

start = time.time()
calc.calxd(img)
end = time.time()
print(end-start)
m,n,x,y = calc.calxm(img)
end2 = time.time()

print(end2-end)

测试结果:非常快,比之前的0.7秒快很多了

一张照片实现CCD相机的效果 PYTHON 用python打开ccd相机_Cv2_03

主程序图表用的是guiqwt,是因为刷新比较快,matplotlib刷新太慢了。

#chart.py

import cv2,time,sys
import numpy as np
from PyQt5.QtWidgets import QWidget, QLabel, QVBoxLayout, QApplication, QHBoxLayout
from PyQt5.QtGui import QImage, QPixmap, QPainter
from PyQt5.QtCore import Qt

import calc
from guiqwt.plot import CurveWidget
from guiqwt.builder import make
from device import ccd_dev

class chart(QWidget):
    def __init__(self):
        super(chart, self).__init__(parent=None)
        #self.resize(800,600)

        self.win1 = CurveWidget()
        self.win1.setFixedWidth(170)
        self.curve = make.curve([],[])#纵向的图表
        self.plot = self.win1.get_plot()
        self.plot.add_item(self.curve)
        self.plot.set_axis_limits(2,0,255)#X轴范围

        self.plot.set_axis_title(0,'Y')#Y轴标签
        self.plot.set_axis_title(2,'Z')#X轴标签

        self.plot.grid.setMajorPen(Qt.DotLine)#大网格实线QtGui.QPen类
        self.plot.grid.setMajorPen(QColor('#e9e9e9'))#大网格颜色
        self.plot.grid.setMinorPen(Qt.NoPen)#小网格不可见

        self.win2 = CurveWidget()
        self.win2.setFixedHeight(150)
        self.curve2 = make.curve([],[])
        self.plot2 = self.win2.get_plot()
        self.plot2.add_item(self.curve2)
        self.plot2.set_axis_limits(0,0,255)
        self.plot2.set_axis_title(0,'Z')
        self.plot2.set_axis_title(2,'X')

        self.plot2.grid.setMajorPen(Qt.DotLine)#大网格实线QtGui.QPen类
        self.plot2.grid.setMajorPen(QColor('#e9e9e9'))#大网格颜色
        self.plot2.grid.setMinorPen(Qt.NoPen)#小网格不可见

        self.lab = QLabel()#显示图片的载体,不知道为什么

        vbox1 = QVBoxLayout()#为了图表曲线高度和图片高度一致
        vbox1.addWidget(self.lab)
        vbox1.addStretch(0)

        hbox = QHBoxLayout()
        hbox.addWidget(self.win1)
        hbox.addLayout(vbox1)

        hbox2 = QHBoxLayout()#为了图表曲线长度和图片长度一致
        hbox2.addStretch(0)
        hbox2.addWidget(self.win2)

        vbox = QVBoxLayout(self)
        vbox.addLayout(hbox)
        vbox.addLayout(hbox2)

        self.th = ccd_dev()#采集图像的分线程
        self.th.datasignal.connect(self.slot_data)
        self.th.start()

    def slot_data(self,i,img):
        #t1 = time.time()
        clr = np.array(img)#Type:list -> np.array,灰度图数据
        height, width = clr.shape
        self.lab.setFixedSize(width,height)
        self.win1.setFixedHeight(height+35)
        self.win2.setFixedWidth(width+47)
        rgb = calc.calxd(clr)#自制彩虹图的算法
        ver, hor, x, y = calc.calxm(clr)#ver是每一行的最大值集合,hor是每一列的最大值集合
        self.curve.set_data(ver,y)#更新图表
        self.curve2.set_data(x,hor)
        image = QImage(rgb,width,height,QImage.Format_RGB888)#QImage.Format_Indexed8显示为灰度图
        self.lab.setPixmap(QPixmap.fromImage(image))#显示图片
        #t2 = time.time()
        #print(t2 - t1)#测量部分代码运行时间


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ar = chart()
    ar.show()
    sys.exit(app.exec_())

界面是这样

一张照片实现CCD相机的效果 PYTHON 用python打开ccd相机_Cv2_04