目录
前言
阐释问题
如图
这是一张河流图,计算河流的长度(厘米),删除并提取需要的河流
对于要删除的河流,我们可以把它涂红,或者涂成某种颜色,然后用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个按钮,
第二个垂直布局是添加的图片
第三个垂直布局是添加去红之后的图片
在第一个垂直布局中
第四个按钮暂时不可见,第一是旋转按钮,可以旋转图片
保存不必多说
其中除了鼠标的点击事件,还有滚轮事件,为了调节笔的大小,向前是增大,向后是减小,画条线
点击结果按钮
得到去红之后的图片,缩小了,太大了,并且出现了清除按钮,用于关闭第三个垂直布局的控件
点击一下键盘的中键
得到数据 ---河流的长度
最终处理
处理得到的图片有痕迹
我选择把图片放到画板里面,用自己的博客
最终结果
还是有点痕迹的
算了