YOLOv5实战记录05 Pyside6可视化界面

个人打卡,慎看。

指路大佬:【手把手带你实战YOLOv5-入门篇】YOLOv5 Pyside6可视化界面_哔哩哔哩_bilibili

零、虚拟环境迁移路径后pip报错解决

yolov5-master文件夹我换位置后,无法pip install了。解决如下:

  • activate.bat中修改:@set "VIRTUAL_ENV=D:\yolov5-master\venv"
  • activate中修改:VIRTUAL_ENV='D:\yolov5-master\venv'
  • 更新pip:python -m pip install --upgrade pip

参考:Python 虚拟环境迁移路径后pip报错解决记录_pybot移动路径后-CSDN博客

 

【建议挂v,速度会快】

一、环境安装

  • 下载Pyside6:  pip install pyside6

我的pyside6不在Python.exe的上级文件夹的子目录下,packages和python.exe的位置也不同,发现解释器的位置设置错了,之前一直提示我Invalid,我也没有注意。

一般python.exe和site-packages的位置关系是,python.exe和它的上级在同一文件夹下。

比如anaconda下python.exe, lib

lib下site-packages ,也可以输入以下命令,查找site-packages的位置。

import site
print(site.getsitepackages())

以后安装了包,但是无法import,也可以查找site-packages的位置,很有可能这里面没有那个包。是python.exe选取的错误,修改为对应的python.exe即可。

  • 找到pyside6/designer.exe,将快捷方式发送到桌面。
  • up主用vscode安装了qt for python插件,但是我使用的是Pycharm,vscode不熟练,在pycharm中,设置setting->tools->external tools,点击+ ,添加,在Program中,输入designer.exe的位置。name和group随便设置,点击ok。

二、设计UI

  1. 新建mainwindow
  2. 把两个textlabel拖到mainwindow中【存放图片】,用vertical line隔离他们。拖两个pushbutton【按钮】。
  3. 勾选scaledcontents可以实现label中存放内容缩放。将alignment设置水平中心对齐。
  4. 修改对象的名字,便于记忆。比如label1改成input
  5. 保存到yolov5-master文件夹下,修改名字为main_window

三、将ui转换成py

添加external tool

我的血泪教训,改这个错改了40min:

  1. program不要选site-packages\Pyside6\uic.exe 这样转换出来的结果可能是C++代码。
  2. 选\Scripts\pyside6-uic.exe这个文件

  • Program: D:\anaconda\Scripts\pyside6-uic.exe 【输入自己的pyside6-uic.exe文件路径】
  • Arguments: $FileName$ -o $FileNameWithoutExtension$.py
  • working directory: $FileDir$

四、调用mainwindow

import sys

from PySide6.QtWidgets import QMainWindow, QApplication

from main_window import Ui_MainWindow
#这里的main_window是你的ui转换成的py名字
#Ui_MainWindow是main_window里的class名字

class MainWindow(QMainWindow,Ui_MainWindow):
    def __init__(self):
        super(MainWindow,self).__init__()   #self后不要加(),我加了()报错无法调用mainwindow
        self.setupUi(self)

if __name__=="__main__":
    app=QApplication(sys.argv)

    window1 = MainWindow()
    window1.show()

    app.exec()

运行上面的指令即可弹出UI设计界面。

五、关联按钮和代码

bing_slots()函数负责绑定。det_image和pushButton_2【忘改了】是那两个按钮的名字。

import sys

from PySide6.QtWidgets import QMainWindow, QApplication,QFileDialog
#QFileDialog可以访问文件资源管理器


from main_window import Ui_MainWindow

class MainWindow(QMainWindow,Ui_MainWindow):
    def __init__(self):
        super(MainWindow,self).__init__()
        self.setupUi(self)
        self.bind_slots()  #不要忘记调用,不写这句话无法显示。

    def open_image(self):
        print("点击显示图片!")


    def open_video(self):
        print("点击显示视频!")

    def bind_slots(self):
        #绑定
        self.det_image.clicked.connect(self.open_image)
        self.pushButton_2.clicked.connect(self.open_video)


if __name__=="__main__":
    app=QApplication(sys.argv)

    window1 = MainWindow()
    window1.show()

    app.exec()

六、获取图片

  • 一开始无法显示,我设置QPixmap("./datasets/images/train/30.jpg")发现可以显示,于是判断是filepath格式的问题;
  • QPixmap的参数是string类型的。打印file_path[0]发现打印出来的仍然是list格式。
  • 于是转换list为string类型。filePath=','.join(file_path[0])
#与上个代码省略了相同的部分,方便理解。

from PySide6.QtGui import QPixmap
#显示图片

    def open_image(self):
        file_path=QFileDialog.getOpenFileNames(self,dir="./datasets/images/train",filter="*.jpg;*.png")
        #设置从dir里找图片, filter分号隔离不同格式
        #file_path返回的是有两个元素的数组,我们运行后,发现第一个元素是路径,第二个元素是格式。我们只输出路径。
        if file_path[0]:
            filePath=','.join(file_path[0])
            print(file_path[0])
            print(filePath)
            self.input.setPixmap(QPixmap(filePath))  #图片显示

七、训练图片

  • 首先,遇到了问题,我运行页面卡住了,一直在下载某个包,经查找,发现是下面这行代码的问题。我运行提示卡在了“'Downloading torch-2.2.2-cp310-cp310-win_amd64.whl (198.6 MB)”,
  • model = torch.hub.load("./","custom",path="runs/train/exp2/weights/best.pt",trust_repo=True, source="local") 运行这行代码时卡住了,其他代码不会卡。
  • 已解决:下载几个包即可:pip install torch torchvision torchaudio
  • 参考:通过pip安装pytorch超时问题解决 - 知乎 (zhihu.com)

运行代码:

import sys
import torch
from PySide6.QtWidgets import QMainWindow, QApplication,QFileDialog
#QFileDialog可以访问文件资源管理器

from PySide6.QtGui import QPixmap,QImage
#显示图片

from main_window import Ui_MainWindow

def convert2QImage(img):
    height,width,channel=img.shape
    return QImage(img,width,height,width*channel,QImage.Format_RGB888)

class MainWindow(QMainWindow,Ui_MainWindow):
    def __init__(self):
        super(MainWindow,self).__init__()
        self.setupUi(self)
        self.model=torch.hub.load("./","custom",path="runs/train/exp2/weights/best.pt",source="local")
        self.bind_slots()  #不要忘记调用,不写这句话无法显示

    def image_pred(self,filePath):
        results=self.model(filePath)
        image=results.render()[0]
        return convert2QImage(image)
        #检测后的图片

    def open_image(self):
        file_path=QFileDialog.getOpenFileNames(self,dir="./datasets/images/train",filter="*.jpg;*.png")
        #设置从dir里找图片, filter分号隔离不同格式
        #file_path返回的是有两个元素的数组,我们运行后,发现第一个元素是路径,第二个元素是格式。我们只输出路径。
        if file_path[0]:
            filePath=','.join(file_path[0])
            qimage=self.image_pred(filePath)
            self.input.setPixmap(QPixmap(filePath))
            self.output.setPixmap(QPixmap.fromImage(qimage))


    def open_video(self):
        print("点击显示视频!")

    def bind_slots(self):
        #绑定
        self.det_image.clicked.connect(self.open_image)
        self.pushButton_2.clicked.connect(self.open_video)


if __name__=="__main__":
    app=QApplication(sys.argv)
    window1 = MainWindow()
    window1.show()

    app.exec()

八、训练视频

仍然遇到了报错:Traceback (most recent call last):
  File "D:\yolov5-master\yolov5-master\base_ui.py", line 57, in open_video
    self.input.setPixmap(QPixmap(convert2QImage(filePath)))
  File "D:\yolov5-master\yolov5-master\base_ui.py", line 13, in convert2QImage
    height,width,channel=imag.shape
AttributeError: 'str' object has no attribute 'shape'

但是我训练图片时, height,width,channel=imag.shape这行代码就没有出现问题,于是查找包含改行代码的函数调用的参数,果然写错了self.input.setPixmap(QPixmap(convert2QImage(frame)))是frame不是filePath。 一定要细心!!!

import sys
import cv2
import torch
from PySide6.QtWidgets import QMainWindow, QApplication,QFileDialog
#QFileDialog可以访问文件资源管理器

from PySide6.QtGui import QPixmap,QImage
#显示图片

from main_window import Ui_MainWindow

def convert2QImage(imag):
    height,width,channel=imag.shape
    return QImage(imag,width,height,width*channel,QImage.Format_RGB888)

class MainWindow(QMainWindow,Ui_MainWindow):
    def __init__(self):
        super(MainWindow,self).__init__()
        self.setupUi(self)
        self.model=torch.hub.load("./","custom",path="runs/train/exp2/weights/best.pt",source="local")
        self.bind_slots()  #不要忘记调用,不写这句话无法显示

    def image_pred(self,filePath):
        results=self.model(filePath)
        image=results.render()[0]
        return convert2QImage(image)
        #检测后的图片
    def video_pred(self,img):
        results=self.model(img)
        image=results.render()[0]
        return convert2QImage(image)

    def open_image(self):
        file_path=QFileDialog.getOpenFileNames(self,dir="./datasets/images/train",filter="*.jpg;*.png")
        #设置从dir里找图片, filter分号隔离不同格式
        #file_path返回的是有两个元素的数组,我们运行后,发现第一个元素是路径,第二个元素是格式。我们只输出路径。
        if file_path[0]:
            filePath=','.join(file_path[0])
            qimage=self.image_pred(filePath)
            self.input.setPixmap(QPixmap(filePath))
            self.output.setPixmap(QPixmap.fromImage(qimage))


    def open_video(self):#视频检测是一帧一帧地检测,利用opencv抽帧。
        file_path = QFileDialog.getOpenFileNames(self, dir="./datasets", filter="*.mp4")

        if file_path[0]:
            filePath=','.join(file_path[0])
            print(filePath)
            video = cv2.VideoCapture(filePath)
            while True:
                ret,frame=video.read()
                if not ret:
                    break
                frame = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)
                qimage=self.video_pred(frame)
                self.input.setPixmap(QPixmap(convert2QImage(frame)))
                self.output.setPixmap(QPixmap.fromImage(qimage))


        print("点击显示视频!")

    def bind_slots(self):
        #绑定
        self.det_image.clicked.connect(self.open_image)
        self.pushButton_2.clicked.connect(self.open_video)


if __name__=="__main__":
    app=QApplication(sys.argv)
    window1 = MainWindow()
    window1.show()

    app.exec()

不过我运行后很慢,并且只显示了视频最后一帧的检测结果和原图片。

问题:界面刷新不及时。

解决:加入计时器from PySide6.QtCore import QTimer
 

import sys
import cv2
import torch
from PySide6.QtWidgets import QMainWindow, QApplication,QFileDialog
#QFileDialog可以访问文件资源管理器

from PySide6.QtGui import QPixmap,QImage
#显示图片
from PySide6.QtCore import QTimer
#计时器

from main_window import Ui_MainWindow

def convert2QImage(imag):
    height,width,channel=imag.shape
    return QImage(imag,width,height,width*channel,QImage.Format_RGB888)

class MainWindow(QMainWindow,Ui_MainWindow):
    def __init__(self):
        super(MainWindow,self).__init__()
        self.setupUi(self)
        self.model=torch.hub.load("./","custom",path="runs/train/exp2/weights/best.pt",source="local")
        self.timer=QTimer()
        self.timer.setInterval(100) #时间间隔。默认单位毫秒ms ,可以设置的小一点儿,视频更加丝滑。
        self.bind_slots()  #不要忘记调用,不写这句话无法显示

    def image_pred(self,filePath):
        results=self.model(filePath)
        image=results.render()[0]
        return convert2QImage(image)
        #检测后的图片
    def video_pred(self):
        ret, frame = self.video.read()
        if not ret:
            self.timer.stop()
        else:
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            self.input.setPixmap(QPixmap.fromImage(convert2QImage(frame)))#放在这儿而不是self.output上面,因为可能受检测的图像影响。在检测之前先显示出来。

            results=self.model(frame)
            image=results.render()[0]
            
            self.output.setPixmap(QPixmap.fromImage(convert2QImage(image)))

    def open_image(self):
        file_path=QFileDialog.getOpenFileNames(self,dir="./datasets/images/train",filter="*.jpg;*.png")
        #设置从dir里找图片, filter分号隔离不同格式
        #file_path返回的是有两个元素的数组,我们运行后,发现第一个元素是路径,第二个元素是格式。我们只输出路径。
        if file_path[0]:
            filePath=','.join(file_path[0])
            qimage=self.image_pred(filePath)
            self.input.setPixmap(QPixmap(filePath))
            self.output.setPixmap(QPixmap.fromImage(qimage))


    def open_video(self):#视频检测是一帧一帧地检测,利用opencv抽帧。
        file_path = QFileDialog.getOpenFileNames(self, dir="./datasets", filter="*.mp4")

        if file_path[0]:
            filePath=','.join(file_path[0])
            print(filePath)
            self.video = cv2.VideoCapture(filePath)
            self.timer.start()

            # while True:
            #     ret,frame=video.read()
            #
            #     if not ret:
            #         break
            #     frame = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)
            #     qimage=self.video_pred(frame)
            #     self.input.setPixmap(QPixmap(convert2QImage(frame)))
            #     self.output.setPixmap(QPixmap.fromImage(qimage))


        print("点击显示视频!")

    def bind_slots(self):
        #绑定
        self.det_image.clicked.connect(self.open_image)
        self.pushButton_2.clicked.connect(self.open_video)
        self.timer.timeout.connect(self.video_pred)


if __name__=="__main__":
    app=QApplication(sys.argv)
    window1 = MainWindow()
    window1.show()

    app.exec()

仍然存在问题,如果再点击“图片检测”按钮,不会停下,这是因为计时器没有停。

解决方案:在打开图片时,关闭计时器,def open_image(self): self.timer.stop()

全部代码:

import sys
import cv2
import torch
from PySide6.QtWidgets import QMainWindow, QApplication,QFileDialog
#QFileDialog可以访问文件资源管理器

from PySide6.QtGui import QPixmap,QImage
#显示图片
from PySide6.QtCore import QTimer
#计时器

from main_window import Ui_MainWindow

def convert2QImage(imag):
    height,width,channel=imag.shape
    return QImage(imag,width,height,width*channel,QImage.Format_RGB888)

class MainWindow(QMainWindow,Ui_MainWindow):
    def __init__(self):
        super(MainWindow,self).__init__()
        self.setupUi(self)
        self.model=torch.hub.load("./","custom",path="runs/train/exp2/weights/best.pt",source="local")
        self.timer=QTimer()
        self.timer.setInterval(1) #默认单位毫秒ms
        self.bind_slots()  #不要忘记调用,不写这句话无法显示

    def image_pred(self,filePath):
        results=self.model(filePath)
        image=results.render()[0]
        return convert2QImage(image)
        #检测后的图片
    def video_pred(self):
        ret, frame = self.video.read()
        if not ret:
            self.timer.stop()
        else:
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            self.input.setPixmap(QPixmap.fromImage(convert2QImage(frame)))
            # 放在这儿而不是self.output上面,因为可能受检测的图像影响。在检测之前先显示出来。
            results=self.model(frame)
            image=results.render()[0]
            self.output.setPixmap(QPixmap.fromImage(convert2QImage(image)))

    def open_image(self):
        self.timer.stop()
        file_path=QFileDialog.getOpenFileNames(self,dir="./datasets/images/train",filter="*.jpg;*.png")
        #设置从dir里找图片, filter分号隔离不同格式
        #file_path返回的是有两个元素的数组,我们运行后,发现第一个元素是路径,第二个元素是格式。我们只输出路径。
        if file_path[0]:
            filePath=','.join(file_path[0])
            qimage=self.video_pred(filePath)
            self.input.setPixmap(QPixmap(filePath))
            self.output.setPixmap(QPixmap.fromImage(qimage))


    def open_video(self):#视频检测是一帧一帧地检测,利用opencv抽帧。
        file_path = QFileDialog.getOpenFileNames(self, dir="./datasets", filter="*.mp4")

        if file_path[0]:
            filePath=','.join(file_path[0])
            print(filePath)
            self.video = cv2.VideoCapture(filePath)
            self.timer.start()

            # while True:
            #     ret,frame=video.read()
            #
            #     if not ret:
            #         break
            #     frame = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)
            #     qimage=self.video_pred(frame)
            #     self.input.setPixmap(QPixmap(convert2QImage(frame)))
            #     self.output.setPixmap(QPixmap.fromImage(qimage))


        print("点击显示视频!")

    def bind_slots(self):
        #绑定
        self.det_image.clicked.connect(self.open_image)
        self.pushButton_2.clicked.connect(self.open_video)
        self.timer.timeout.connect(self.video_pred)


if __name__=="__main__":
    app=QApplication(sys.argv)
    window1 = MainWindow()
    window1.show()

    app.exec()

九、复盘

  1. 首先利用Pyside6的designer设计了UI界面。两个button,两个label,一条线。导出生成ui文件。将ui文件编译生成成py文件,注意external tool选的是pyside6-uic.exe,不是uic.exe!!!
  2. 构建程序,设计函数:初始化;绑定按键与函数事件;打开图片;打开视频;图片预测;视频预测【预测部分利用了torch.hub,实际上是对每一帧的图片进行预测,利用了计时器】


今天最大的收获就是可以平静地对待报错了,之前从大一开始每次改报错都特别烦躁,今天竟然可以平静地坐着改几个小时的bug。我之前特别怵头以后做机器学习、python相关的研究,现在不怕了。

要相信你遇到的问题,别人也遇到过,并且他们解决了。

如果别人没有遇到过,那正好,你会是第一个发现问题并解决的人,之后的人都会借鉴你的解决方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/521125.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

刷题之Leetcode844题(超级详细)

844.比较退格的字符串 844. 比较含退格的字符串https://leetcode.cn/problems/backspace-string-compare/ 给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。 注意:如…

5G网络架构及技术(二):OFDM一

ToDo: 等把这些讲义看完后得单开一个文章整理思维导图   该部分由于内容比较重要,OFDM是5G物理层的基础,但学习时直接跳到5G OFDM去看它的那些参数设置感觉没什么意义,还得从发展的角度进行学习,先从最先用到OFDM的WiFi协议开始…

CSS-属性

📚详见 W3scholl,本篇只做快速思维索引。 CSS 背景 用于定义元素的背景效果。 background-colorbackground-imagebackground-positionbackground-repeatbackground-attachment background-color background-color 属性指定元素的背景色。 h1 {back…

专题【链表】【考试题】刷题日记

题目列表 考试题(22题) 2024.04.04 146. LRU 缓存 707. 设计链表 138. 随机链表的复制 160. 相交链表 622. 设计循环队列 109. 有序链表转换二叉搜索树 460. LFU 缓存 355. 设计推特 725. 分隔链表 2487. 从链表中移除节点 日常复习题 876. 链表的中…

机器学习(理论第一课)

一、理解人工智能、机器学习、深度学习、强化学习? 人工智能、机器学习和深度学习之间存在递进关系,它们的覆盖范围逐层递减。 **人工智能(Artificial Intelligence,AI)**是最宽泛的概念,旨在研究、开发用于…

好物周刊#49:字幕交流网站

https://yuque.com/cunyu1943 村雨遥的好物周刊,记录每周看到的有价值的信息,主要针对计算机领域,每周五发布。 一、项目 1. Starship 轻量、迅速、可无限定制的高颜值终端,可用于各种 Shell 的提示符。 2. spring cloud shop …

Web3 游戏周报(3.24-3.30)

【3.24-3.30】Web3 游戏行业动态: Web3 开发平台 Mirror World 在 Solana 上推出首个游戏 rollup 链 NFT 卡牌游戏 Parallel 完成 3,500 万美元融资,Solana Ventures 等参投 加密游戏开发公司 Gunzilla Games 完成 3,000 万美元融资 Telegram 游戏 No…

【C语言自定义类型之----结构体,联合体和枚举】

一.结构体 1.结构体类型的声明 srruct tag {nemer-list;//成员列表 }varible-list;//变量列表结构体在声明的时候,可以不完全声明。 例如:描述一个学生 struct stu {char name[20];//名字int age;//年龄char sex[20];//性别 };//分号不能省略2.结构体…

静态模板编译:提高Web性能的利器

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…

SpringBoot3整合RabbitMQ之二_简单队列模型案例

SpringBoot3整合RabbitMQ之二_简单队列模型案例 文章目录 SpringBoot3整合RabbitMQ之二_简单队列模型案例1. 简单队列模型1. 消息发布者1. 创建简单队列的配置类2. 发布消费Controller 2. 消息消费者3. 输出结果 1. 简单队列模型 简单队列模型就是点对点发布消息,有…

(二)小案例银行家应用程序-创建DOM元素

● 上图的数据很明显是从我们账户数组中拿到了,我们刚刚学习了forEach,所以我们使用forEach来创建我们的DOM元素; const displayMovements function (movements) {movements.forEach((mov, i) > {const type mov > 0 ? deposit : w…

Nacos 入门篇---客户端如何发起服务注册?怎么发送服务心跳的(二)

一、引言 上个章节我们简单学习和使用了下Nacos服务自动注册,本文就来分析下Nacos客户端自动注册服务是怎么实现的~ 二、目录 目录 一、引言 三、Nacos 源码编译 1.1 拉取代码 1.2 运行起来 四、客户端使用版本选择 五、Nacos客户端项目启动为什么会…

c++的学习之路:14、list(1)

本章讲一下如何使用list 一、list介绍 首先还是看一看官方文档的介绍如下图,如下方五点: 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。 2. list的底层是双向链表结构,双向链…

阿里微服务质量保障系列:域内测试

进入阿里之前,我就职的公司所在部门的产品都是单体应用,例如第一家公司是做投顾平台的,第二家公司所在的团队是做在线教育的,负责的产品是内容生产平台。投顾平台这个产品是服务于券商投顾员工的,属于券商内部应用&…

公开课学习——仿抖音直播平台

文章目录 直播抖音的直播原理Java继承直播客户端工具: ffmpeg客户端和网页集成CDN网络——性能提升关键——边缘计算 实时聊天——IM系统怎么实现?——websocketIM系统消息如何转发?直播场景IM系统是什么样子? 直播 抖音的直播原…

R语言实现:统计学及计量专业中的多种平均值计算方式

平均值在计量专业和统计学中有着广泛的应用如:描述数据集中趋势、比较不同组数据、评估数据的代表性、决策和判断、回归分析概率统计与财务分析等。此外,在计量专业中,平均值还被广泛应用于各种测量和校准过程中,以确保测量结果的…

34.Python从入门到精通—Python3 正则表达式检索和替换

34.从入门到精通:Python3 正则表达式检索和替换 repl 参数是一个函数 正则表达式对象 正则表达式修饰符 - 可选标志 正则表达式模式* 正则表达式实例 检索和替换repl 参数是一个函数正则表达式对象正则表达式修饰符 - 可选标志正则表达式模式*正则表达式实例 检索和…

动规训练4

目录 一、买股票的最佳实际含冷冻期 1、题目解析 2、算法原理 a状态表示方程 b状态转移方程 c初始化 d填表顺序 e返回值 3、代码 4、感想 二、买股票的最佳时机函手续费 1、题目解析 2、算法原理 a状态表示方程 b状态转移方程 c初始化 d填表顺序 e返回值 3、…

STM3定时器输入捕获、超声波测距

1、超声波测距模块介绍 1、HC-SR04共四个引脚:VCC、GND、Trig、Echo,如下图 2、使用 1、通过gpio口向Trig引脚发送一个脉冲信号。 2、HC-SR04接收到脉冲信号后,就会向外发送一段超声波,模块会将echo拉高。 …

Web CSS笔记3

一、边框弧度 使用它你就可以制作盒子边框圆角 border-radius:1个值四个圆角值相同2个值 第一个值为左上角与右下角,第二个值为右上角与左下角3个值第一个值为左上角, 第二个值为右上角和左下角,第三个值为右下角4个值 左上角,右…