基于Python+Pyqt+Opencv的气浮图片气泡特征识别

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/nhddipzxh/article/details/82829979

       最近在使用我们的新型防堵型释放器做气浮试验,以探究释放器的一些物理特性。其中,气泡沿软管长度的形态变化(气泡量、气泡粒径)需要重点关注。故试验过程中使用单反拍摄了大量(1000张左右)照片,照片如图1所示。软管中有少量粒径较大气泡,大部分粒径较小,较难直接观测。

                                                                                        图1 软管中的气泡分布

        为了验证观测的可行性,使用Photoshop调图片的亮度、对比度后进行锐化,锐化图片如图2所示。从锐化图片可以明显看到微小气泡的存在。为了验证统计的可行性,特地让师弟(在这里特别感谢李林原同学)从图片中截取了几个断面做气泡直径分析。气泡直径分析?说起来好像挺高端,其实就是用Photoshop的标尺来量像素。软管的外径是已知的,像素也是可测量的,一个简单的比例的关系就可以算出来气泡大小。

                                                                                         

                                                                                         图2 锐化后图片

        但进度不是很理想,仅一张图片我师弟就忙活了整整一天,想想夹子里总共1000张左右的图片,就跟他开玩笑说:慢慢数吧,数完了你硕士就毕业了:)

         所以痛点就在于方案是可行的,但效率是不足的。正巧手上有一本毛星云著的《OpenCV3编程入门》,OpenCV3在图像处理方面功能还是蛮强大的,Photoshop可以实现的功能,理论上都可以使用OpenCV编码来实现。但OpenCV的短板就在于控件只有一个Slider,功能太过于单一,Label、Button、textbox啥啥的啥都没有,这就需要做一个Client程序来集成OpenCV。读硕士之前及当中我是断断续续做过一年左右的码农的(这里要感谢硕导的开明以及高胜经理的培养),但基本上都是做B/S结构的项目,C/S结构的基本上不怎么涉及,所以选择一个功能强大的,轻量级的,短时间可以上手的桌面环境就极为重要。上述书中使用的环境为VC++,这个环境下貌似只有Windows API和MFC可选择,使用Windows API做桌面程序简直就是噩梦,一条条API慢慢查,慢慢堆,果断放弃。MFC封装好一些,但是学习周期较长,且是上世纪末的产物,现在基本淘汰(当然,市面上还能看到不少MFC的程序,可能也算与老程序员兼容吧:))。在Visual Studio中,还有C sharp可以选择,但C sharp我也不会,且C sharp下如何调用OpenCV我也完全没有概念。最终,我还是把注意力集中在了Pyqt上,我其实从博一就开始关注Pyqt了,只是一直也没有地方用,所以也就只是看看了文档和例程,仅仅有些感性认识。Pyqt的优点在于:1、可移植性好;2、与Python的兼容性高;3、Python牛逼性强;4、门槛低;关于可移植性,Pyqt脱胎于qt,Windows、Linux(包括类Linux,如MAC)、Android通吃,完全是一处编码,处处运行。关于Python的牛逼之处,我就不多说了,搞科研的都懂。关于门槛低,Pyqt本身还带辅助界面设计程序Qt designer,使用pyuic5可以直接将生成的UI文件转换成py文件,直接在py文件里面写逻辑就行。做出来的第一个版本如图3所示。

                                          

                                                                                         图3 第一版程序

        本来想将图片加载后在图中的QLabel(也就是灰框框)里面显示,但我显然还是太高估了自己的水平(也是因为对Pyqt、Python、numpy根本不熟),所以最终还是选择了妥协,使用OpenCV的imshow()来显示图片。如何显示不重要,重要的是功能的实现。

                                                                                     图4 第二版程序

        当功能基本都实现后,还是对UI进行了调整,调整后的程序如图5所示。

                                             

                                             

                                                                                     图5 第三版程序

到这里,就基本上搞定了,识别出来的气泡边缘如图6所示(这是原照片中的一个小的局部,本来识别出来的气泡应该是圆形的,但由于拍摄设备能力有限,以及光影、折射、散射、算法等因素的原因,导致识别出来的气泡并不圆,不过由于气泡本身直径非常小,这些误差暂时忽略不计)。

                                                      

                                                                                        图6 气泡边缘识别图

        边缘特征参数记录如图7所示。

                              

                                                                                          图7 边缘特征参数

      通过轮廓的面积和周长即可计算出气泡直径(这部分就不写逻辑了,获得数据后,拖拖Excel就搞定了)

      源代码如下(按逻辑能够跑下来,但bug还没有扫,bug交给师弟慢慢扫:>)

import sys
import cv2
import numpy as np
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication, QWidget, QFileDialog,QMainWindow,QMessageBox 
from PyQt5.QtGui import QImage ,QPixmap, QPainter
from math import *

winName="My Picture"
zoom=0
fname=None #保存图片信息
pic=None #保存无损图片
rotatePic=None #保存无损旋转图片
picPath=None #保存无损图片路径
newPic=None #最终生成的无损图片
handlePic=None #处理中的图片
modiflyPic=[] #识别处理图片
thresholdPic=None#阈值处理图片
zoomWidth=0
zoomHeight=0
picRead=0
leftPoint=None
rightPoint=None
rotateZoompic=None#旋转处理的图片
rotate=0

class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(500, 159)
        self.tabWidget = QtWidgets.QTabWidget(Form)
        self.tabWidget.setGeometry(QtCore.QRect(10, 10, 480, 131))
        self.tabWidget.setObjectName("tabWidget")
        self.tab = QtWidgets.QWidget()
        self.tab.setObjectName("tab")
        self.openPic = QtWidgets.QPushButton(self.tab)
        self.openPic.setGeometry(QtCore.QRect(5, 10, 90, 30))
        self.openPic.setObjectName("openPic")
        self.zoomUp = QtWidgets.QPushButton(self.tab)
        self.zoomUp.setGeometry(QtCore.QRect(120, 10, 90, 30))
        self.zoomUp.setObjectName("zoomUp")
        self.zoomDown = QtWidgets.QPushButton(self.tab)
        self.zoomDown.setGeometry(QtCore.QRect(240, 10, 90, 30))
        self.zoomDown.setObjectName("zoomDown")
        self.savePic = QtWidgets.QPushButton(self.tab)
        self.savePic.setGeometry(QtCore.QRect(360, 10, 90, 30))
        self.savePic.setObjectName("savePic")
        self.rotate = QtWidgets.QSlider(self.tab)
        self.rotate.setGeometry(QtCore.QRect(10, 50, 321, 31))
        self.rotate.setProperty("value", 0)
        self.rotate.setProperty("maximum",180)
        self.rotate.setProperty("minimum",-180)
        self.rotate.setOrientation(QtCore.Qt.Horizontal)
        self.rotate.setObjectName("rotate")
        self.label = QtWidgets.QLabel(self.tab)
        self.label.setGeometry(QtCore.QRect(168, 85, 20, 20))
        self.label.setObjectName("label")
        self.label_2 = QtWidgets.QLabel(self.tab)
        self.label_2.setGeometry(QtCore.QRect(0, 85, 51, 20))
        self.label_2.setObjectName("label_2")
        self.label_3 = QtWidgets.QLabel(self.tab)
        self.label_3.setGeometry(QtCore.QRect(310, 85, 41, 20))
        self.label_3.setObjectName("label_3")
        self.label_7 = QtWidgets.QLabel(self.tab)
        self.label_7.setGeometry(QtCore.QRect(380, 60, 90, 12))
        self.label_7.setObjectName("label_7")
        self.tabWidget.addTab(self.tab, "")
        self.tab_2 = QtWidgets.QWidget()
        self.tab_2.setObjectName("tab_2")
        self.openBubpic = QtWidgets.QPushButton(self.tab_2)
        self.openBubpic.setGeometry(QtCore.QRect(5, 10, 90, 30))
        self.openBubpic.setObjectName("openBubpic")
        self.lightMod = QtWidgets.QSlider(self.tab_2)
        self.lightMod.setGeometry(QtCore.QRect(105, 15, 300, 20))
        self.lightMod.setProperty("value", 0)
        self.lightMod.setProperty("maximum",200)
        self.lightMod.setProperty("minimum",0)
        self.lightMod.setOrientation(QtCore.Qt.Horizontal)
        self.lightMod.setObjectName("lightMod")
        self.recogPic = QtWidgets.QPushButton(self.tab_2)
        self.recogPic.setGeometry(QtCore.QRect(5, 42, 90, 30))
        self.recogPic.setObjectName("recogPic")
        self.savBubdata = QtWidgets.QPushButton(self.tab_2)
        self.savBubdata.setGeometry(QtCore.QRect(5, 75, 90, 30))
        self.savBubdata.setObjectName("savBubdata")
        self.contrastMod = QtWidgets.QSlider(self.tab_2)
        self.contrastMod.setGeometry(QtCore.QRect(105, 50, 300, 20))
        self.contrastMod.setProperty("value", 100)
        self.contrastMod.setProperty("maximum",300)
        self.contrastMod.setProperty("minimum",100)
        self.contrastMod.setOrientation(QtCore.Qt.Horizontal)
        self.contrastMod.setObjectName("contrastMod")
        self.sharpMod = QtWidgets.QSlider(self.tab_2)
        self.sharpMod.setGeometry(QtCore.QRect(105, 80, 300, 20))
        self.sharpMod.setProperty("value", 120)
        self.sharpMod.setProperty("maximum",255)
        self.sharpMod.setProperty("minimum",0)
        self.sharpMod.setOrientation(QtCore.Qt.Horizontal)
        self.sharpMod.setObjectName("sharpMod")
        self.label_4 = QtWidgets.QLabel(self.tab_2)
        self.label_4.setGeometry(QtCore.QRect(410, 15, 54, 12))
        self.label_4.setObjectName("label_4")
        self.label_5 = QtWidgets.QLabel(self.tab_2)
        self.label_5.setGeometry(QtCore.QRect(410, 50, 60, 12))
        self.label_5.setObjectName("label_5")
        self.label_6 = QtWidgets.QLabel(self.tab_2)
        self.label_6.setGeometry(QtCore.QRect(410, 80, 54, 12))
        self.label_6.setObjectName("label_6")
        self.tabWidget.addTab(self.tab_2, "")
        #信号绑定
        self.openPic.clicked.connect(self.openfile)
        self.zoomUp.clicked.connect(self.zoomUpfun)
        self.zoomDown.clicked.connect(self.zoomDownfun)
        self.rotate.sliderReleased.connect(self.rotateZoompicfun)
        self.lightMod.sliderReleased.connect(self.lightModify)
        self.contrastMod.sliderReleased.connect(self.contrastModify)
        self.sharpMod.sliderReleased.connect(self.sharpModify)
        self.recogPic.clicked.connect(self.recogPicfun)
        self.savePic.clicked.connect(self.writefile)
        self.retranslateUi(Form)
        self.tabWidget.setCurrentIndex(0)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "Form"))
        self.openPic.setText(_translate("Form", "打开图片"))
        self.zoomUp.setText(_translate("Form", "放大"))
        self.zoomDown.setText(_translate("Form", "缩小"))
        self.savePic.setText(_translate("Form", "保存图片"))
        self.label.setText(_translate("Form", "0°"))
        self.label_2.setText(_translate("Form", "-180°"))
        self.label_3.setText(_translate("Form", "180°"))
        self.label_7.setText(_translate("Form", "旋转图片"))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("Form", "图片修改"))
        self.openBubpic.setText(_translate("Form", "打开图片"))
        self.recogPic.setText(_translate("Form", "识别边缘"))
        self.savBubdata.setText(_translate("Form", "保存数据"))
        self.label_4.setText(_translate("Form", "亮度调节"))
        self.label_5.setText(_translate("Form", "对比度调节"))
        self.label_6.setText(_translate("Form", "二值化阈值"))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("Form", "气泡识别"))
    #信号处理槽
    #打开文件
    def openfile(self):
        fname=QFileDialog.getOpenFileName(self.openPic,'请选择图片','.',"Image Files(*.jpg *.png)")
        if fname[0]:
            global pic
            global picRead
            global zoom
            global leftPoint
            global rightPoint
            global handlePic
            global zoomWidth
            global zoomHeight
            pic=self.cv_imread(fname[0])
            #转为灰度图
            pic=cv2.cvtColor(pic,cv2.COLOR_BGR2GRAY);
            picRead=1
            if pic.data==0:
                print("图片读取错误\n")
                return
            zoom=0
            handlePic=None
            leftPoint=None
            rightPoint=None
            zoomWidth=0
            zoomHeight=0
            if pic.shape[1]>pic.shape[0]:
                zoomWidth=500
                zoomHeight=pic.shape[0]*500/pic.shape[1]
            else:
                zoomHeight=500
                zoomWidth=pic.shape[1]*500/pic.shape[0]
            handlePic=cv2.resize(pic,(int(zoomWidth),int(zoomHeight)),interpolation=cv2.INTER_AREA)
            self.finalpic(handlePic)
    #OpemCv中文路径处理
    def cv_imread(self,filePath):
        cv_img=cv2.imdecode(np.fromfile(filePath,dtype=np.uint8),-1)
        return cv_img
    #保存高清图片
    def writefile(self):
        if picRead==0:
            print("没有加载图片\n")
            return
        fname=QFileDialog.getSaveFileName(self.savePic,"保存图片文件",'.',"Image Files(*.jpg *.png)")
        picWrite=cv2.imwrite(fname[0],newPic)
        if picWrite==0:
            print("保存图片失败!")
        
    #显示图片
    def finalpic(self,finalPic):
        cv2.namedWindow(winName,cv2.WINDOW_AUTOSIZE)
        cv2.setMouseCallback(winName,self.on_mouse)
        key=cv2.imshow(winName,finalPic)
    #鼠标事件响应
    def on_mouse(self, event, x, y, flags, frames):
        global leftPoint
        global rightPoint
        global handlePic
        global rotateZoompic
        global zoomWidth
        global zoomHeight
        global rotatePic
        global pic
        global newPic
        if picRead==0:
            print("没有加载图片\n")
            return
        if event==cv2.EVENT_LBUTTONDOWN:
            leftPoint=(x,y)
        elif event==cv2.EVENT_MOUSEMOVE:
            rightPoint=(x,y)
        elif event==cv2.EVENT_LBUTTONUP:
            #zoomPic=zoomPic(cv2.Rect(leftPoint.x,leftPoint.y,(rightPoint.x-leftPoint.x),(rightPoint.y-leftPoint.y)))
            if rotate==1:
                multiple=rotatePic.shape[1]/rotateZoompic.shape[1]
                handlePic=rotateZoompic[leftPoint[1]:rightPoint[1],leftPoint[0]:rightPoint[0]]
                newPic=rotatePic[int(leftPoint[1]*multiple):int(rightPoint[1]*multiple),int(leftPoint[0]*multiple):int(rightPoint[0]*multiple)]
            else:
                multiple=pic.shape[1]/handlePic.shape[1]
                handlePic=handlePic[leftPoint[1]:rightPoint[1],leftPoint[0]:rightPoint[0]]
                newPic=pic[int(leftPoint[1]*multiple):int(rightPoint[1]*multiple),int(leftPoint[0]*multiple):int(rightPoint[0]*multiple)]
            zoomWidth=handlePic.shape[1]
            zoomHeight=handlePic.shape[0]
            self.finalpic(handlePic)       
    #图片放大
    def zoomUpfun(self):
        global zoom
        global handlePic
        global zoomWidth
        global zoomHeight
        if picRead==0:
            print("没有加载图片\n")
            return
        zoom+=1
        if rotate==1:
            handlePic=cv2.resize(rotateZoompic,(int(rotateZoompic.shape[1]*(1+0.1*zoom)),int(rotateZoompic.shape[0]*(1+0.1*zoom))),interpolation=cv2.INTER_AREA)
        else:
            handlePic=cv2.resize(handlePic,(int(handlePic.shape[1]*(1+0.1*zoom)),int(handlePic.shape[0]*(1+0.1*zoom))),interpolation=cv2.INTER_AREA)
        zoomWidth=handlePic.shape[1]
        zoomHeight=handlePic.shape[0]
        cv2.destroyWindow(winName)
        self.finalpic(handlePic)
    #图片缩小
    def zoomDownfun(self):
        global zoom
        global handlePic
        global zoomWidth
        global zoomHeight
        if picRead==0:
            print("没有加载图片\n")
            return
        zoom-=1
        if rotate==1:
            handlePic=cv2.resize(rotateZoompic,(int(rotateZoompic.shape[1]*(1+0.1*zoom)),int(rotateZoompic.shape[0]*(1+0.1*zoom))),interpolation=cv2.INTER_AREA)
        else:
            handlePic=cv2.resize(handlePic,(int(handlePic.shape[1]*(1+0.1*zoom)),int(handlePic.shape[0]*(1+0.1*zoom))),interpolation=cv2.INTER_AREA)
        handlePic=cv2.resize(handlePic,(int(handlePic.shape[1]*(1+0.1*zoom)),int(handlePic.shape[0]*(1+0.1*zoom))),interpolation=cv2.INTER_AREA)
        zoomWidth=handlePic.shape[1]
        zoomHeight=handlePic.shape[0]
        cv2.destroyWindow(winName)
        self.finalpic(handlePic)
    #旋转图片
    def rotateZoompicfun(self):
        global handlePic
        global zoomWidth
        global zoomHeight
        global rotateZoompic
        global rotate
        global rotatePic
        global pic
        if picRead==0:
            print("没有加载图片\n")
            return
        else:
            angle=self.rotate.value()
            #旋转缩放后图片——仿射变换
            heightZoomnew=int(zoomWidth*fabs(sin(radians(angle)))+zoomHeight*fabs(cos(radians(angle))))
            widthZoomnew=int(zoomHeight*fabs(sin(radians(angle)))+zoomWidth*fabs(cos(radians(angle))))
            rot=cv2.getRotationMatrix2D((zoomWidth/2,zoomHeight/2),angle,1)
            rot[0,2]+=(widthZoomnew-zoomWidth)/2
            rot[1,2]+=(heightZoomnew-zoomHeight)/2
            rotateZoompic=cv2.warpAffine(handlePic,rot,(widthZoomnew,heightZoomnew))
            #旋转高清图片——仿射变换
            heightnew=int(pic.shape[1]*fabs(sin(radians(angle)))+pic.shape[0]*fabs(cos(radians(angle))))
            widthnew=int(pic.shape[0]*fabs(sin(radians(angle)))+pic.shape[1]*fabs(cos(radians(angle))))
            rot=cv2.getRotationMatrix2D((pic.shape[1]/2,pic.shape[0]/2),angle,1)
            rot[0,2]+=(widthnew-pic.shape[1])/2
            rot[1,2]+=(heightnew-pic.shape[0])/2
            rotatePic=cv2.warpAffine(pic,rot,(widthnew,heightnew))
            rotate=1;
            self.finalpic(rotateZoompic)
    #亮度调节
    def lightModify(self):
        global modiflyPic
        global newPic
        if picRead==0:
            print("没有加载图片\n")
            return
        modiflyPic=newPic
        width=newPic.shape[1]
        height=newPic.shape[0]
        for row in range(0,height):
            for col in range(0,width):
                modiflyPic[row,col]=self.checkPixels((self.contrastMod.value()*0.01)*newPic[row,col]+self.lightMod.value())
        self.finalpic(modiflyPic)
    #对比度调节
    def contrastModify(self):
        global modiflyPic
        if picRead==0:
            print("没有加载图片\n")
            return
        modiflyPic=newPic
        width=newPic.shape[1]
        height=newPic.shape[0]
        for row in range(0,height):
            for col in range(0,width):
                modiflyPic[row,col]=self.checkPixels((self.contrastMod.value()*0.01)*newPic[row,col]+self.lightMod.value())
        self.finalpic(modiflyPic)     
    #阈值检查
    def checkPixels(self,value):
        if value<0:
            value=0
        elif value>255:
            value=255
        return int(value)
    #二值化阈值调节
    def sharpModify(self):
        global modiflyPic
        global thresholdPic
        ret,thresholdPic=cv2.threshold(modiflyPic,self.sharpMod.value(),255,cv2.THRESH_BINARY)#与opencv不同,返回两个参数
        self.finalpic(thresholdPic)
    #识别边缘
    def recogPicfun(self):
        global thresholdPic
        global modiflyPic
        image,contours,hierarchy=cv2.findContours(thresholdPic, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE);
        img=cv2.drawContours(modiflyPic,contours,-1,(0,0,255),3)
        cv2.imshow('ttt',img)
if __name__ == '__main__':
   """
   主函数
   """
   app = QApplication(sys.argv)
   #app = QApplication(sys.argv),每一个pyqt程序必须创建一个application对象,
   #sys.argv是命令行参数,可以通过命令启动的时候传递参数。
   mainWindow = QWidget()
   #生成过一个实例(对象), mainWindow是实例(对象)的名字,可以随便起。
   ui = Ui_Form()
   ui.setupUi(mainWindow)
   mainWindow.show()  #用来显示窗口
   sys.exit(app.exec_())#exec_()方法的作用是“进入程序的主循环直到exit()被调

       

猜你喜欢

转载自blog.csdn.net/nhddipzxh/article/details/82829979