文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Arduino开发ESP32-CAM模块 & 使用Python-PyQt5编写图传.exe独立程序

2023-08-22 20:02

关注

1.ESP32-CAM WiFi获取视频流以及保存图像到TF卡

1.1 驱动ESP32-CAM

笔者使用Arduino编写ESP32-CAM的驱动程序,版本为1.8.19。在较新的版本中,Arduino的UI风格发生了变化,不过下面配置的功能基本保留,读者注意辨别其中的异同之处。

1.1.1 在Arduino中配置开发环境

首先,我们需要在Arduino中配置ESP32开发板的开发环境。打开Arduino,按如下路径依次点击:“文件” → \rightarrow “首选项”,找到“附加开发板管理器网址”,如图1.1所示。
在这里插入图片描述

图1.1 找到“附加开发板管理器网址”

在这里插入图片描述

图1.2 输入附加网址的界面

按照界面上“一行一个”的指示,将下面两个网址输入进去:

https://dl.espressif.com/dl/package_esp32_index.json

https://github.com/espressif/arduino-esp32/releases/download/2.0.2/package_esp32_dev_index.json

然后点击“好”即可。

1.1.2 在Arduino中配置开发板

接下来我们需要配置开发板。按如下路径依次点击:“工具” → \rightarrow “开发板” → \rightarrow “开发板管理器”,弹出界面如图1.3:
在这里插入图片描述

图1.3 开发板管理器

在显示有“对搜索进行过滤…”字样的搜索框内输入“ESP32”,显示界面如图1.4:
在这里插入图片描述

图1.4 搜索ESP32开发板

选择版本2.0.2,然后点击“安装”,等待其安装好即可。

安装好后,按如下路径点击:“工具” → \rightarrow “开发板” → \rightarrow “ESP32 Arduino” → \rightarrow “AI Thinker ESP32 CAM”。至此,我们就配置好了ESP32的开发环境和开发板,可以进行下一步的开发了。

1.1.3 驱动ESP32-CAM

由于官方的库并不能驱动ESP32-CAM,因此在此处我参考了CSDN用户“ShemuelHe”的博客。博客链接为本节最后的参考资料当中的第二个链接。在此处,我们需要使用GitHub上大神yoursunny用户所提供的库。下载链接为本节参考资料的第一个链接。点进他的主页后,如图1.5所示:
在这里插入图片描述

图1.5 yoursunny的github主页

点击“Download ZIP”,将代码压缩包下载下来。然后回到Arduino,按如下路径点击:“项目” → \rightarrow “加载库” → \rightarrow “添加.ZIP库”,弹出界面如图1.6所示:
在这里插入图片描述

图1.6 加载库的界面

找到刚才下载的库的路径,找到.ZIP文件(该压缩包不需要解压),选中后点击“打开”。这样,这个库就添加好了。其他项目中,如果要添加非官方库,也可以通过这样的方式。

1.1.4 完整的驱动代码(WiFi接入热点、将视频流通过WiFi发送、识别TF卡、存储照片到TF卡)

完整Arduino代码(经过测试,直接复制可用)

#include #include #include #include #include #include #include #include #include #include "cJSON.h"#include "FS.h"#include "esp_camera.h"//以下改成要连接的WiFi名称和密码const char* WIFI_SSID = "******"; const char* WIFI_PASS = "******";WebServer server(80);static auto loRes = esp32cam::Resolution::find(320, 240);static auto hiRes = esp32cam::Resolution::find(1280, 1024);//UXGA:分辨率为1600*1200的输出格式,SXGA(1280*1024)、XVGA(1280*960)、WXGA(1280*800)、XGA(1024*768)、SVGA(800*600)、VGA(640*480)、CIF(352*288)和QQVGA(160*120)等。char path[] = "/1.jpg";int order = 1;void handleBmp(){  if (!esp32cam::Camera.changeResolution(loRes)) {    Serial.println("SET-LO-RES FAIL");  }  auto frame = esp32cam::capture();  if (frame == nullptr) {    Serial.println("CAPTURE FAIL");    server.send(503, "", "");    return;  }  Serial.printf("CAPTURE OK %dx%d %db\n", frame->getWidth(), frame->getHeight(),                static_cast(frame->size()));  if (!frame->toBmp()) {    Serial.println("CONVERT FAIL");    server.send(503, "", "");    return;  }  Serial.printf("CONVERT OK %dx%d %db\n", frame->getWidth(), frame->getHeight(),                static_cast(frame->size()));  server.setContentLength(frame->size());  server.send(200, "image/bmp");  WiFiClient client = server.client();  frame->writeTo(client);}void serveJpg(){  auto frame = esp32cam::capture();  if (frame == nullptr) {    Serial.println("CAPTURE FAIL");    server.send(503, "", "");    return;  }  Serial.printf("CAPTURE OK %dx%d %db\n", frame->getWidth(), frame->getHeight(),                static_cast(frame->size()));  server.setContentLength(frame->size());  server.send(200, "image/jpeg");  WiFiClient client = server.client();  frame->writeTo(client);}void handleJpgLo(){  if (!esp32cam::Camera.changeResolution(loRes)) {    Serial.println("SET-LO-RES FAIL");  }  serveJpg();}void handleJpgHi(){  if (!esp32cam::Camera.changeResolution(hiRes)) {    Serial.println("SET-HI-RES FAIL");  }  serveJpg();}void handleJpg(){  server.sendHeader("Location", "/cam-hi.jpg");  server.send(302, "", "");}void handleMjpeg(){  if (!esp32cam::Camera.changeResolution(hiRes)) {    Serial.println("SET-HI-RES FAIL");  }  Serial.println("STREAM BEGIN");  WiFiClient client = server.client();  auto startTime = millis();  int res = esp32cam::Camera.streamMjpeg(client);  if (res <= 0) {    Serial.printf("STREAM ERROR %d\n", res);    return;  }  auto duration = millis() - startTime;  Serial.printf("STREAM END %dfrm %0.2ffps\n", res, 1000.0 * res / duration);}// Init SD Cardvoid sd_init(){  //The argument ("/sdcard",true) means closing LED light on the board  if (!SD_MMC.begin("/sdcard",true)) {     Serial.println("Card Mount Failed");    return;  }  uint8_t cardType = SD_MMC.cardType();  if (cardType == CARD_NONE) {    Serial.println("No SD card attached");    return;  }  Serial.print("SD Card Type: ");  if (cardType == CARD_MMC) {    Serial.println("MMC");  }  else if (cardType == CARD_SD) {    Serial.println("SDSC");  }  else if (cardType == CARD_SDHC) {    Serial.println("SDHC");  }  else {    Serial.println("UNKNOWN");  }//Get the size of SD card, unit: MB  uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024);     Serial.printf("SD 卡容量大小: %lluMB\n", cardSize);}void setup(){  Serial.begin(115200);  Serial.println();  {    using namespace esp32cam;    Config cfg;    cfg.setPins(pins::AiThinker);    cfg.setResolution(hiRes);    cfg.setBufferCount(2);    cfg.setJpeg(80);    bool ok = Camera.begin(cfg);    Serial.println(ok ? "CAMERA OK" : "CAMERA FAIL");  }  sd_init();  delay(5000);  WiFi.persistent(false);  WiFi.mode(WIFI_STA);  WiFi.begin(WIFI_SSID, WIFI_PASS);  while (WiFi.status() != WL_CONNECTED) {    delay(500);  }  Serial.print("http://");  Serial.println(WiFi.localIP());  Serial.println("  /cam.bmp");  Serial.println("  /cam-lo.jpg");  Serial.println("  /cam-hi.jpg");  Serial.println("  /cam.mjpeg");  server.on("/cam.bmp", handleBmp);  server.on("/cam-lo.jpg", handleJpgLo);  server.on("/cam-hi.jpg", handleJpgHi);  server.on("/cam.jpg", handleJpg);  server.on("/cam.mjpeg", handleMjpeg);  server.begin();}void loop(){  server.handleClient();  camera_fb_t * fb = esp_camera_fb_get();  sprintf(path,"/%d.jpg",order);  if (fb == NULL)  {    Serial.println( "Get picture failed");  }  else  {    fs::FS &fs = SD_MMC;    Serial.printf("Writing file: %s\n", path);    File file = fs.open(path, FILE_WRITE);    if (!file)    {      Serial.println("Failed to Create a File!");    }    else    {      file.write(fb->buf , fb->len);    }    esp_camera_fb_return(fb);    order += 1;  }}

最终跑出来的效果如图1.7、1.8所示:

Python读取URL获取视频流
在这里插入图片描述

图1.7 Python小程序读取视频流

图中“图像显示”的程序是通过Python编写,这一部分涉及PyQt5的使用,将在1.2节详细介绍。

保存拍摄的照片到TF卡
在这里插入图片描述

图1.8 保存到TF卡中的照片

注意: ESP32-CAM的TF卡槽最多支持4G容量的TF卡,超过此容量的TF卡均不能被成功识别。

此部分参考资料如下:

GitHub - yoursunny/esp32cam: OV2640 camera on ESP32-CAM, Arduino library / https://github.com/yoursunny/esp32cam

获取视频流的ESP32代码包

【ESP32-CAM】使用opencv获取ESP32-CAM视频流(一)_ShemuelHe的博客-CSDN博客_esp32移植opencv / https://blog.csdn.net/ShemuelHe/article/details/121365730?utm_source=app&app_version=5.0.1&code=app_1562916241&uLinkId=usr1mkqgl919blen

WiFi获取视频流,python openCV实现视频流获取

(ESP32学习16)ESP32_CAM获取图片并且保存文件名为当前时间_bird1999625的博客-CSDN博客 / https://blog.csdn.net/ailta/article/details/106866261

CAM摄像头拍摄图像并保存至TF卡

1.2 Python PyQt 5编写图像显示的.exe程序

在1.1.4节中介绍了电脑端读取URL图像,获取视频流的效果。该节中的参考链接2中提供了OpenCV的方式来读取视频流。而由于我们需要将程序移植到不同的电脑上使用,因此需要将Python脚本打包成.exe执行文件。这一节将介绍如何使用Python读取URL图像并将整个程序打包成.exe可执行文件,使其在没有安装Python开发环境的电脑上也能运行。

1.2.1 安装Python

此部分内容在网上有很多详细的资料,此处不再赘述,可参考本节末尾的参考资料当中的第一个链接,相当详细,将每一步都列举了出来,按操作即可成功安装。

1.2.2 安装PyQt5包

打开PyCharm,新建工程和py脚本,然后按照如下路径依次点击:“文件” → \rightarrow “设置” → \rightarrow “项目” → \rightarrow “Python解释器”,界面如图1.9所示:
在这里插入图片描述

图1.9 Python解释器界面

点击红圈当中的“+”按钮,进入下面的界面:
在这里插入图片描述

图1.10 Python安装库的界面

在左上角画红色线的搜索栏中输入“PyQt5”;然后选中右下角橙色线处“指定版本”,选择最新的版本;最后点击左下角红圈圈住的“安装软件包”,等待安装这个包即可。

同样,在此工程中,需要安装“PyQt5-tools”包。操作方法同上。

安装好了之后,找到这两个包的安装位置。笔者的位置如下:
在这里插入图片描述

图1.11 PyQt5及tools包的安装位置

这两个包安装在路径“\UITest\venv\Lib\site-packages”中。

1.2.3 添加外部工具

按如下路径依次点击:“文件” → \rightarrow “设置” → \rightarrow “项目” → \rightarrow “工具” → \rightarrow “外部工具” ,进入如下界面:
在这里插入图片描述

图1.12 外部工具界面

点击上图中左上角的“+”,弹出如下窗口:
在这里插入图片描述

图1.13 外部工具添加与配置界面

在“名称”栏中,输入外部工具的名字,在这里我们将其命名为“QtDesigner”;在“程序栏”中,输入“designer.exe”的路径;在“工作目录”栏中,输入“$FileDir$”。其中,“designer.exe”的路径如下:
在这里插入图片描述

图1.16 designer.exe的路径

输入好后,界面如下:
在这里插入图片描述

图1.17 配置好后编辑工具的界面

同样,我们需要添加外部工具“pyuic5.exe”程序。该程序将QtDesigner中设计好的UI界面转化成Python脚本,供我们编程开发使用。在“外部工具”中,再点一次“+”,将该工具添加进来:
在这里插入图片描述

图1.18 添加pyuic5.exe

我们将该工具命名为PyUIC,“程序”一栏添加pyuic5.exe文件的路径;“实参”一栏添加如下信息:$FileName$ -o $FileNameWithoutExtension$.py;“工作目录”一栏添加:$ProjectFileDir$。然后点击“确定”。这样,我们就添加好了我们所需要的外部工具。

1.2.4 QtDesigner的使用

在1.2.3节中,我们下载好了开发.exe文件所需要的软件包,并添加好了外部工具。至此,准备工作已经全部完成,我们可以开始使用QtDesigner来开发我们的软件了。

打开Qt:在PyCharm顶端的菜单栏中,按照如下顺序点击:“工具” → \rightarrow “External tools” → \rightarrow “QtDesigner”:
在这里插入图片描述

图1.19 打开QtDesigner

打开后,界面如图1.20所示:
在这里插入图片描述

图1.20 QtDesigner的界面

左侧为常用的一些控件树;中间的部分为设计工具提供给我们的一些模板;右侧为控件树和当前选中的控件的一些属性。在这里,我们选择“Main Window”,点击“Create”,界面如图1.21所示:
在这里插入图片描述

图1.21 选择Main Window后设计工具的界面

这个时候,我们就可以添加各种控件并给它们配置属性,以达到我们的目的。

关于如何布局,读者可参考白月黑羽的教程,相当详细Python Qt 图形界面编程 - PySide2 PyQt5 PyQt PySide_哔哩哔哩_bilibili / https://www.bilibili.com/video/BV1cJ411R7bP?spm_id_from=333.999.0.0,此文档中不再赘述。读者需要尤其注意Layout的使用。本项目中需要使用PyQt5开发的程序较为简单,就是读取URL获取视频流,因此,笔者所设计的UI布局如图1.22所示:
在这里插入图片描述

图1.22 笔者所设计的UI界面

在这个界面中,我使用了一个Label控件用来显示图像,三个按钮控件来触发事件,一个文本框用来输入URL地址。控件添加完成后,使用Layout进行布局。

1.2.5 美化控件(QSS)

在QtDesigner中,我们可以通过QSS的方式来美化控件。此处我以按钮控件为例,简要介绍QSS的使用。

选中按钮控件,在属性栏中,找到“Qwidget” → \rightarrow “Stylesheet”,点击“StyleSheet”右侧的三点按钮:
在这里插入图片描述

图1.23 进入QSS的路径

弹出界面如图1.24所示:
在这里插入图片描述

图1.24 QSS代码编写窗口

在这个编辑框里,我们可以输入如下格式的代码:

QPushButton {// 按钮一般属性    background-color: white ;    font-size:16px;    color:black;    border-radius: 15px;//圆角半径    font-family:微软雅黑;//字体    background:rgb(255, 255, 255);//背景颜色    border:2px solid black;//边框宽度}QPushButton:hover{ //鼠标悬浮在按钮上时按钮的属性    background:rgb(237, 108, 0, 150);//鼠标悬浮时背景颜色为rgb(237,108,0,150)}QPushButton:pressed{ //鼠标按下时按钮的属性    background:white;//按下鼠标时背景颜色为白色}

然后点击“OK”即可。这个时候,控件的外观就会按照我们代码所设定的样子显示出来。

1.2.6 将UI转化成.py文件

设计好UI界面后,我们就需要将UI文件转化成.py文件,在PyCharm编辑器中编写程序了。

在左侧的文件预览器中,找到我们的.ui文件,右键单击,然后左键依次点击:“External tools” → \rightarrow “PyUIC”:
在这里插入图片描述

图1.25 PyUIC的开启路径

点击后,会生成一个和.ui文件同名的.py文件。控制台和文件树如下所示:
在这里插入图片描述

图1.26 转化得到的.py文件的控制台和文件树

双击打开VideoShow.py文件,这时候编辑器就会显示我们所创建的UI对应的.py文件代码了。

1.2.7 运行出ui

当我们有了.ui文件对应的.py文件后,我们就需要将这个ui使用代码运行起来,最终实现我们想要的功能。在这一节,我将以项目中的图像显示为例,简要介绍如何将我们创建的ui在PyCharm中运行出来。

在.ui文件和刚才生成的.py文件同一个文件夹下新建一个.py文件,在这里,我将其命名为“Test.py”。

添加如下代码:

import sysimport requestsimport VideoShowfrom PyQt5.QtWidgets import QMainWindow, QApplicationfrom PyQt5.QtGui import QImage, QPixmap, QIconclass MainWindows(QMainWindow, VideoShow.Ui_ShowVideo):    def __init__(self, parent=None):        QMainWindow.__init__(self, parent)        self.setupUi(self)if __name__ == '__main__':    app = QApplication(sys.argv)    main = MainWindows()    # 设置软件窗口的名字    main.setWindowTitle('Submarine 图像显示')     # 设置软件的图标。注意,"icon_16.png"文件需要和工程在同一文件夹下。    main.setWindowIcon(QIcon("icon_16.png"))    # 显示UI    main.show()    sys.exit(app.exec_())    

运行上述代码,我们就可以看到我们刚才设计的UI界面了:
在这里插入图片描述

图1.27 运行代码得到的程序UI界面

1.2.8 信号和槽函数

在1.2.7节,我们成功地将我们的UI界面运行了起来。但是,这个时候这个界面是没有什么用的——我们还没有给相关控件添加功能代码,使它们发挥各自的作用。在这里,我们就需要给控件编写信号和槽函数的相关代码了。关于信号和槽的基本概念,在1.2.4节的链接中也有较为详细的介绍。简单来说,当我们点击按钮时,发送一个信号,这个信号被连接到一个槽函数当中,该函数就会执行相应的功能代码。在这个工程中,我们需要给三个按钮编写槽函数,并且开启一个定时器,每隔一段时间读取一次URL。在这一节中,我将简单介绍如何编写功能代码。

控件命名

在QtDesigner中的控件树中,我们可以给我们的控件命名:
在这里插入图片描述

图1.28 命名控件

例如此处,我将按钮控件分别命名为“CloseAppButton”、“CloseVideoButton”、“OpenVideoButton”。在PyUIC生成的代码中,我们如果想要调用这几个控件,就需要调用这些变量名。

槽函数的编写

首先,我们编写一下“打开视频”这个按钮的槽函数。代码如下:

def onOpenVideoButtonClicked(self):    self.timer.start(20)  # 设置计时间隔并启动,间隔20ms    self.VideoShowLabel.setScaledContents(True)    

在这个函数中,我们将定时器启动,并设置读取时间间隔为20ms;设置显示图像的Label控件为自适应图片大小。那么,这个槽函数名就叫做“onOpenVideoButtonClicked”,“打开视频”按钮被按下后,程序就会执行该函数当中的代码。

将信号连接到槽函数上

编写好了槽函数后,我们需要将信号连接到槽函数上。比如,我们需要将“打开视频”按钮被点击的信号连接到刚才我们编写的槽函数上,以使槽函数执行功能。连接信号的代码如下:

self.OpenVideoButton.clicked.connect(self.onOpenVideoButtonClicked)

这样,当我们运行UI后,点击这些控件,程序就会执行相应的功能了。

整个App的代码如下:

import sysimport requestsimport VideoShowfrom PyQt5.QtGui import QImage, QPixmap, QIconfrom PyQt5.QtWidgets import QMainWindow, QApplicationfrom PyQt5.QtCore import QTimerclass MainWindows(QMainWindow, VideoShow.Ui_ShowVideo):    def __init__(self, parent=None):        QMainWindow.__init__(self, parent)        self.setupUi(self)        # 初始化一个定时器        self.timer = QTimer(self)        # 计时结束调用operate()方法        self.timer.timeout.connect(self.operate)        # 将开启信号与槽函数关联        self.OpenVideoButton.clicked.connect(self.onOpenVideoButtonClicked)        self.CloseVideoButton.clicked.connect(self.onCloseVideoButtonClicked)        self.CloseAppButton.clicked.connect(self.onCloseAppButtonClicked)    def onOpenVideoButtonClicked(self):        self.timer.start(20)  # 设置计时间隔并启动,间隔20ms        self.VideoShowLabel.setScaledContents(True)    def onCloseVideoButtonClicked(self):        self.timer.stop()  # 关闭定时器    def onCloseAppButtonClicked(self):        self.timer.stop()  # 关闭定时器        self.close() # 关闭应用程序        # 定时器的处理函数    def operate(self):        url = self.URLEdit.text() # 获取URL        # print(url)        res = requests.get(url)        img = QImage.fromData(res.content)        self.VideoShowLabel.setPixmap(QPixmap.fromImage(img))        self.show()if __name__ == '__main__':    app = QApplication(sys.argv)    main = MainWindows()    main.setWindowTitle('Submarine 图像显示')    main.setWindowIcon(QIcon("icon_16.png"))    main.show()    sys.exit(app.exec_())

这样,我们就编写好了一个简单的UI界面。

1.2.9 封装打包

和1.2.2节安装PyQt5包一样,我们需要将“pyinstaller”这个包下载下来:
在这里插入图片描述

图1.29 下载安装pyinstaller包

点击“安装软件包”,等待这个包安装好即可。

在控制台下端找到“终端”并点击,出现如下界面:
在这里插入图片描述

图1.30 进入PyCharm的终端界面

然后在上面的窗口中输入如下指令:

pyinstaller -F -w -i icon.ico Test.py  

其中,“-F”表示打包后只生成一个.exe文件(也可以理解为覆盖掉之前产生的同名.exe文件);“-w”表示不使用控制台;“-i”表示改变生成的.exe文件的图标,后面要跟上图标文件的文件名和格式。一般这里支持.ico格式,读者可以在PhotoShop中制作好自己的图标并将其放在工程文件夹下。最后,添加上我们要打包的.py文件名。

常用选项及说明如下:

按下回车,等待打包完毕:
在这里插入图片描述

图1.31 打包完成时终端的显示内容

打开我们的工程文件夹下的“dist”文件夹,如下图所示:
在这里插入图片描述

图1.32 .exe文件的路径

可以看到我们刚才生成的.exe文件了。将其复制到我们存放.ico文件的文件夹中(否则图标将不会显示)并双击打开,程序就可以正常运行了:
在这里插入图片描述

图1.33 双击.exe文件运行出的程序UI界面

至此,我们就制作完成了图像传输的简单.exe程序。该程序可以在没有安装python环境的计算机中运行。

此部分参考资料如下:

PyCharm2021安装教程_学习H的博客-CSDN博客_pycharm2021安装教程

将python程序打包成exe_蹦跶的小羊羔的博客-CSDN博客_python打包成exe

Python Qt 图形界面编程 - PySide2 PyQt5 PyQt PySide_哔哩哔哩_bilibili

Python Qt 简介 | 白月黑羽 (byhy.net)

来源地址:https://blog.csdn.net/Zhuwany/article/details/128989573

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯