pyqt5与opencv 解决图上河流长度的问题

        目录

前言

阐释问题

说明图片

红色的提取

长度的计算

最终代码

其中关键操作

操作

整体

最终处理

最终结果

前言

阐释问题

如图

 

 这是一张河流图,计算河流的长度(厘米),删除并提取需要的河流

对于要删除的河流,我们可以把它涂红,或者涂成某种颜色,然后用opencv提取出来

很明显是可以的,这里选择涂红。

说明图片

图片是用像素作为单位的,但要求实际的长度,这就需要一点转换

在这位大佬的博客中,给出解决的办法

使用Python提取JPEG图像文件dpi并计算物理尺寸_dongfuguo的博客-CSDN博客

这个转换里面有个非常重要的东西,dpi,或者说分辨率,不是指图片的尺寸。

而在其中的分辨率是一图片本身的分辨率,实际上,得到图片是通过打印机

总之,一张图片的实际大小是由图片本身的尺寸和打印机的分辨率(我可能说错了,我是这么理解的)决定的

结合大佬的博客来段代码实验一下

from PIL import Image
from init import desktop_path
filename=desktop_path+'/2.jpg'
def get_w_h(filename, precision=2):
    img=Image.open(filename)
    width,height=img.size
    wpi,hpi=img.info['dpi']
    w = round(width / wpi * 25.4, precision)
    h = round(height / hpi* 25.4, precision)
    print(w,h)
    print(width,height)
def get_dpi(filename,w,h):
    w*=10
    h*=10
    img=Image.open(filename)
    width,height=img.size
    ws=w/25.4
    hs=h/25.4
    wpi=round(width/ws)
    hpi=round(height/hs)
    print(wpi,hpi)

运行一下

get_w_h(filename)

结果

81.74 116.96
708 1013

结果说明,图片是尺寸是708*1013,在图片本身的分辨率下,得到的实际尺寸是81.74和116.96毫米

但是我用其他分辨率100,100

def get_w_hs(filename,precision=2):
    img=Image.open(filename)
    width,height=img.size
    wpi=100
    hpi=100
    w = round(width / wpi * 25.4, precision)
    h = round(height / hpi* 25.4, precision)
    print(w,h)
    print(width,height)
get_w_hs(filename)

结果如下:

179.83 257.3
708 1013
结果不必多说

红色的提取

把图上的某一条黑色的线变成红色

提取其中的红色,用opencv

import cv2 as cv
import numpy as np
class River:
    def __init__(self,picture=None):
        self.picture =cv.imread('4.jpg')
    def get_mask(self):
        img = self.picture
        hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
        red_lower = np.array([0, 44, 46])
        red_high = np.array([10, 255, 255])
        mask1 = cv.inRange(hsv, red_lower, red_high)
# inRange是主要的函数,二值化处理
# 红色在hsv的参照表有两个部分,所以合并一下
        red_start = np.array([156, 44, 46])
        red_end = np.array([180, 255, 255])
        mask2 = cv.inRange(hsv, red_start, red_end)
        mask = mask1 + mask2
        return mask
# 返回掩膜
    def get_one_channel_pic(self,):
# 得到单通道
        return cv.cvtColor(self.picture,cv.COLOR_BGR2GRAY)
    def add(self):
        img_s=self.get_one_channel_pic()
        mask=self.get_mask()
        add_img = cv.add(img_s, mask)
# 图片的相加
        return add_img
    def return_picture(self,picture):
        self.picture=cv.cvtColor(picture,cv.COLOR_BGR2RGB)
        return self.picture
# 静态方法,为了pyqt5的调用
    @staticmethod
    def main():
        r=River()
        img=r.add()
        return img

实验一下

我涂成这样

 提取后

 还是可以的,但是还是有痕迹的存在,我表示无法直接解决,可以后续操作

长度的计算

在pyqt5中,设置点击事件,

左键点击一个点,保存到一个列表中,又继续点击,直到选择完一条河流。

点击中键,计算距离

点击右键,清空列表

总体是这样计算的

看代码

    def mousePressEvent(self, e):
        info=e.buttons()
        if info == Qt.LeftButton:
            x=e.x()
            y=e.y()
            self.data.append([x,y])
# 左键添加点
        if info==Qt.RightButton:
            self.data.clear()
            self.another_data.clear()
# 右键清除点
        if info==Qt.MidButton:
            if self.data:
                self.another_data.append(self.data[0])
                self.get_distance(self.another_data)
# 把其中的数据放到另一个列表里面
            else:
                pass
# 求self.data的长度,一旦大于2,就画直线
        if len(self.data)==2:
            self.paint.begin(self.picture)
            self.paint.setPen(self.pen)
            self.paint.drawLine(QLine(self.data[0][0],self.data[0][1],self.data[1][0],self.data[1][1]))
            self.another_data.append(self.data[0])
# 并且放到另一个列表里,删除data的第一个数据
            del self.data[0]
            self.paint.end()
            self.update()

    def get_distance(self,data):
        def ahead_one(a):
            b = a.pop(0)
            a.append(b)
            return a
# 把每个元素提前一位
        datas = copy.deepcopy(data)
# 深拷贝
        data=ahead_one(data)
        n=len(data)-1
        del datas[n]
        del data[n]
# 去掉最后一个元素,自己实验过就知道为什么要去掉这一步
        for i,t in zip(data,datas):
            x1=i[0]
            y1=i[1]
            x2=t[0]
            y2=t[1]
            self.distance=self.__get_reatily(x1,x2,y1,y2)
        self.display_distance()
        self.distance=0
# 计算实际距离
# 欧式距离
    def __get_reatily(self,x1,x2,y1,y2):
        width = math.fabs(x2 - x1)
        width = round(width / self.wpi * 25.4, 2) / 10
        height = math.fabs(y2 - y1)
        height = round(height / self.hpi * 25.4, 2) / 10
        x = width ** 2
        y = height ** 2
        z = x + y
        z = math.sqrt(z)
        self.distance += z
        return self.distance
    def display_distance(self):
        print('当前河流的长度为',self.distance)

最终代码

import math
import sys
from PIL import Image
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import copy
import cv2 as cv
import numpy as np
class Board(QMainWindow):
    def __init__(self,filename=None,picture=None):
        super().__init__()
        self.pen_size=10
        self.pen = QPen(Qt.red, self.pen_size, Qt.SolidLine)

        self.wpi=None
        self.hpi=None
        self.resize(1800, 1000)
        self.distance = 0
        self.rotateAction = None
        self.pix = None
        self.data=[]
        self.another_data=[]
        if filename:
            self.picture=QPixmap(filename)
            self.img = Image.open(filename)
            self.image = QImage(filename)
        else:
            self.picture=picture
            self.img=None
            self.image=None
        self.paint=QPainter(self)

        self.initui()
    def initui(self):
        self.wpi,self.hpi=115,115
    def slotRotate(self):
        if self.image.isNull():
            return
        transform = QTransform()
        transform.rotate(90)
        self.image = self.image.transformed(transform)
        self.picture=QPixmap.fromImage(self.image)
        self.update()
    def set_picture(self,picture:QPixmap):
        self.picture=picture
    @property
    def get_picture(self):
        return self.picture
    def paintEvent(self, e):
        if self.picture:
            self.paint.begin(self)
            self.paint.drawPixmap(0,0,self.picture)
            self.paint.end()
    def wheelEvent(self, e):
        a=e.angleDelta()
        if a.y()>0:
            self.pen_size+=1
            print(self.pen_size)
        else:
            self.pen_size-=1
        self.pen=QPen(Qt.red, self.pen_size, Qt.SolidLine)

    def mousePressEvent(self, e):
        info=e.buttons()
        if info == Qt.LeftButton:
            x=e.x()
            y=e.y()
            self.data.append([x,y])
        if info==Qt.RightButton:
            self.data.clear()
            self.another_data.clear()
        if info==Qt.MidButton:
            if self.data:
                self.another_data.append(self.data[0])
                self.get_distance(self.another_data)
            else:
                pass
        if len(self.data)==2:
            self.paint.begin(self.picture)

            self.paint.setPen(self.pen)
            self.paint.drawLine(QLine(self.data[0][0],self.data[0][1],self.data[1][0],self.data[1][1]))
            self.another_data.append(self.data[0])
            del self.data[0]
            self.paint.end()
            self.update()

    def get_distance(self,data):
        def ahead_one(a):
            b = a.pop(0)
            a.append(b)
            return a
        datas = copy.deepcopy(data)
        data=ahead_one(data)
        n=len(data)-1
        del datas[n]
        del data[n]
        for i,t in zip(data,datas):
            x1=i[0]
            y1=i[1]
            x2=t[0]
            y2=t[1]
            self.distance=self.__get_reatily(x1,x2,y1,y2)
        self.display_distance()
        self.distance=0
    def __get_reatily(self,x1,x2,y1,y2):
        width = math.fabs(x2 - x1)
        width = round(width / self.wpi * 25.4, 2) / 10
        height = math.fabs(y2 - y1)
        height = round(height / self.hpi * 25.4, 2) / 10
        x = width ** 2
        y = height ** 2
        z = x + y
        z = math.sqrt(z)
        self.distance += z
        return self.distance
    def display_distance(self):
        print('当前河流的长度为',self.distance)
    def save(self):
        image = self.picture.toImage()
        return image
    def again(self,picture):
        self.picture=QPixmap.fromImage(picture)
        self.update()
    def updates(self):
        self.update()
class Main(QWidget):
    def __init__(self,filename):
        super().__init__()
        self.clear_board = None
        self.v2 = None
        self.end_pic = None
        self.filename = None
        self.board=Board(filename)
        self.another_board=None
        self.initui()
        self.setWindowTitle('河流')
        self.resize(1980,900)
    def initui(self):
        self.set_layout()
    def set_layout(self):
        h=QHBoxLayout(self)
        v=QVBoxLayout(self)
        v1=QVBoxLayout(self)
        self.v2=QVBoxLayout(self)


        save_btn=QPushButton('保存',self)
        end=QPushButton('结果',self)
        rotate = QPushButton("顺旋", self)
        self.clear_board=QPushButton('清除',self)
        self.clear_board.clicked.connect(self.clearBoard)
        self.clear_board.setVisible(False)
        rotate.clicked.connect(self.slotRotate)
        end.clicked.connect(self.updatas)
        save_btn.clicked.connect(self.save_clicked)

        v1.addWidget(self.board)
        v.addWidget(rotate)
        v.addWidget(save_btn)
        v.addWidget(end)
        v.addWidget(self.clear_board)

        h.addLayout(v)
        h.addLayout(v1)
        h.addLayout(self.v2)
    def clearBoard(self):
        self.clear_board.setVisible(False)
        self.another_board.close()
    def slotRotate(self):
        self.board.slotRotate()
    def save_clicked(self):
        path,ok = QFileDialog.getSaveFileName(self, '保存图片', '.\\', '(*.jpg *.png)')
        if ok:
            img=self.board.save()
            img.save(path)
    def updatas(self):
        picture=self.board.get_picture
        original_image = picture.toImage()
        image1 = self.convertQImageToMat(original_image)
        image = cv.cvtColor(image1, cv.COLOR_BGRA2BGR)
        img=River.main(picture=image)
        h, w, _ = img.shape
        img = QImage(img.data, w, h,w*3,QImage.Format_RGB888)
        img=QPixmap.fromImage(img)
        self.another_board=Board(picture=img)
        self.clear_board.setVisible(True)
        widget_count=self.v2.count()
        if widget_count==0:
            self.v2.addWidget(self.another_board)
        else:
            self.delete_widget()
            self.v2.addWidget(self.another_board)
    def delete_widget(self):
        item = self.v2.itemAt(0)
        self.v2.removeItem(item)
        if item.widget():
            item.widget().deleteLater()

    def convertQImageToMat(self, incomingImage):
        incomingImage = incomingImage.convertToFormat(4)
        width = incomingImage.width()
        height = incomingImage.height()
        ptr = incomingImage.bits()
        ptr.setsize(incomingImage.byteCount())
        arr = np.array(ptr).reshape(height, width, 4)
        return arr
class River:
    def __init__(self,picture=None):
        self.picture = picture
    def get_mask(self):
        img = self.picture
        hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
        red_lower = np.array([0, 100, 100])
        red_high = np.array([10, 255, 255])
        mask1 = cv.inRange(hsv, red_lower, red_high)
        red_start = np.array([160, 100, 100])
        red_end = np.array([179, 255, 255])
        mask2 = cv.inRange(hsv, red_start, red_end)
        mask = mask1 + mask2
        return mask
    def get_one_channel_pic(self,):
        return cv.cvtColor(self.picture,cv.COLOR_BGR2GRAY)
    def add(self):
        img_s=self.get_one_channel_pic()
        mask=self.get_mask()
        add_img = cv.add(img_s, mask)
        return add_img
    def return_picture(self,picture):
        picture=cv.resize(picture,dsize=None,fx=0.5,fy=0.5)
        self.picture=cv.cvtColor(picture,cv.COLOR_BGR2RGB)
        return self.picture
    @staticmethod
    def main(picture):
        r=River(picture=picture)
        img=r.add()
        return r.return_picture(img)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyle(QStyleFactory.create('Fusion'))
    a = Main("3.jpg")
    a.show()
    sys.exit(app.exec_())

其中关键操作

除了在前言中说的,其中

    def updatas(self):
        picture=self.board.get_picture
        original_image = picture.toImage()
# pixmap到qimage
        image1 = self.convertQImageToMat(original_image)
# qimage到mat
        image = cv.cvtColor(image1, cv.COLOR_BGRA2BGR)
        img=River.main(picture=image)
# 提取红色
        h, w, _ = img.shape
        img = QImage(img.data, w, h,w*3,QImage.Format_RGB888)
# mat到qimage
        img=QPixmap.fromImage(img)
# qimage到pixmap
        self.another_board=Board(picture=img)
# 创建一个控件
        self.clear_board.setVisible(True)
        widget_count=self.v2.count()
# 判断布局中是否只要一个控件,有就删除,没有就加加进去
# 保持一个,防止重叠
        if widget_count==0:
            self.v2.addWidget(self.another_board)
        else:
            self.delete_widget()
            self.v2.addWidget(self.another_board)
    def delete_widget(self):
# 删除操作
        item = self.v2.itemAt(0)
        self.v2.removeItem(item)
        if item.widget():
            item.widget().deleteLater()
# qimage到mat
    def convertQImageToMat(self, incomingImage):
        incomingImage = incomingImage.convertToFormat(4)
        width = incomingImage.width()
        height = incomingImage.height()
        ptr = incomingImage.bits()
        ptr.setsize(incomingImage.byteCount())
        arr = np.array(ptr).reshape(height, width, 4)
        return arr

直言的说,我复制这两位大佬的代码

[pyQt5]QPixmap(QImage)类型图片转换为Mat格式图片遇到的坑_pyqt5 qimage_臧初之的博客-CSDN博客

PyQt5入门——删除、清空layout布局中的所有对象(含常见问题详解)_pyqt5怎么删除框架但不删除内容_虾米小馄饨的博客-CSDN博客

操作

整体

 

运行后,整体是上是一个水平布局,其中有三个垂直布局,

第一个垂直布局添加4个按钮,

第二个垂直布局是添加的图片

第三个垂直布局是添加去红之后的图片

在第一个垂直布局中

第四个按钮暂时不可见,第一是旋转按钮,可以旋转图片

保存不必多说

其中除了鼠标的点击事件,还有滚轮事件,为了调节笔的大小,向前是增大,向后是减小,画条线

点击结果按钮

 得到去红之后的图片,缩小了,太大了,并且出现了清除按钮,用于关闭第三个垂直布局的控件

点击一下键盘的中键

得到数据 ---河流的长度

最终处理

处理得到的图片有痕迹

 我选择把图片放到画板里面,用自己的博客

pyqt5制作简单的画板_疏狂难除的博客-CSDN博客

最终结果

 还是有点痕迹的

算了

猜你喜欢

转载自blog.csdn.net/qq_63401240/article/details/129911529