Esp32-Cam图像识别
一、网页显示视频流1、Linux式例程2、MicroPython式例程步骤1、下载Thonny步骤2、烧录Esp32-Cam固件步骤3、运行相应代码 3、Arduino式例程步骤1、下载Arduino步骤2、安装Esp32-Cam库步骤3、选择例程步骤4、查看运行结果 二、半小时内实现图像识别1、网页视频流2、通过视频流采集目标并训练步骤1、新建Spyder工程步骤2、训练数据获取步骤3、数据处理并建立模型 3、生成代码移植到Esp32-Cam(1)将HOG和RF算法转换为可以在 Esp32-cam 上运行的C++代码(2)创建Arduino项目工程(3)烧录到Esp32-Cam
这个项目可以让你在半个小时内实现模型训练和图像识别,非常简单。
开始前先放效果视频点击这里
一、网页显示视频流
现成资源有很多,只要稍微找下然后把程序烧录到Esp32-Cam都可以实现该功能。详细内容前往学习即可,此处不赘述。
1、Linux式例程
可以学习安信可官网的例程,权威。点击前往
教程很详细,有Linux基础的兄弟可以尝试一下,否则就别在这个上面折腾了(比如vim编辑器使用、shell脚本使用、linux配置等,都很费时间,而且寡人也没尝试成功)
2、MicroPython式例程
这种方式是让Esp32-Cam具备python环境,能够运行py文件。点击前往
步骤1、下载Thonny
下载地址:https://thonny.org/
步骤2、烧录Esp32-Cam固件
使用Thonny如果烧录固件后无法显示boot.py文件的话应该是底板有问题,可以去买指定的相应底板,但其实使用USB转ttl,杜邦线对应接5V、GND、TXD和RXD就可以了。
步骤3、运行相应代码
import socketimport networkimport cameraimport time# 连接wifiwlan = network.WLAN(network.STA_IF)wlan.active(True)if not wlan.isconnected(): print('connecting to network...') wlan.connect('dongfeiqiu', 'wangmingdong1225') while not wlan.isconnected(): passprint('网络配置:', wlan.ifconfig()) # 摄像头初始化try: camera.init(0, format=camera.JPEG)except Exception as e: camera.deinit() camera.init(0, format=camera.JPEG)# 其他设置:# 上翻下翻camera.flip(1)#左/右camera.mirror(1)# 分辨率camera.framesize(camera.FRAME_HVGA)# 选项如下:# FRAME_96X96 FRAME_QQVGA FRAME_QCIF FRAME_HQVGA FRAME_240X240# FRAME_QVGA FRAME_CIF FRAME_HVGA FRAME_VGA FRAME_SVGA# FRAME_XGA FRAME_HD FRAME_SXGA FRAME_UXGA FRAME_FHD# FRAME_P_HD FRAME_P_3MP FRAME_QXGA FRAME_QHD FRAME_WQXGA# FRAME_P_FHD FRAME_QSXGA# 有关详细信息,请查看此链接:https://bit.ly/2YOzizz# 特效camera.speffect(camera.EFFECT_NONE)#选项如下:# 效果\无(默认)效果\负效果\ BW效果\红色效果\绿色效果\蓝色效果\复古效果# EFFECT_NONE (default) EFFECT_NEG \EFFECT_BW\ EFFECT_RED\ EFFECT_GREEN\ EFFECT_BLUE\ EFFECT_RETRO# 白平衡# camera.whitebalance(camera.WB_HOME)#选项如下:# WB_NONE (default) WB_SUNNY WB_CLOUDY WB_OFFICE WB_HOME# 饱和camera.saturation(0)#-2,2(默认为0). -2灰度# -2,2 (default 0). -2 grayscale # 亮度camera.brightness(0)#-2,2(默认为0). 2亮度# -2,2 (default 0). 2 brightness# 对比度camera.contrast(0)#-2,2(默认为0).2高对比度#-2,2 (default 0). 2 highcontrast# 质量camera.quality(10)#10-63数字越小质量越高# socket UDP 的创建s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)try: while True: buf = camera.capture() # 获取图像数据 s.sendto(buf, ("192.168.31.53", 9090)) # 向服务器发送图像数据 time.sleep(0.1)except: passfinally: camera.deinit()
3、Arduino式例程
这个也是我发现最简单的实现例程,而且资源也多,涉及的语言主要是C++。点击前往
步骤1、下载Arduino
下载地址:点击前往
步骤2、安装Esp32-Cam库
方法一:在IDE安装。
(1). 文件 → 首选项→附加开发板管理器网址,修改网址为
https://arduino.esp8266.com/stable/package_esp8266com_index.jsonhttps://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
(2). 工具 → 开发板 → 开发板管理器,搜索esp32,点击安装即可
方法二:github下载zip压缩包作为库
下载地址:点击前往
下载zip压缩包完成后,项目 →包含库 →添加.ZIP库
步骤3、选择例程
工具 →开发板 →esp32 →AI Thinker ESP32-CAM
在如下位置里边填充wifi和密码
const char* ssid = "Your wifi name";const char* password = "wifi password";
完整代码截取如下
#include "esp_camera.h"#include <WiFi.h>//// WARNING!!! Make sure that you have either selected ESP32 Wrover Module,// or another board which has PSRAM enabled//// Select camera model//#define CAMERA_MODEL_WROVER_KIT//#define CAMERA_MODEL_ESP_EYE//#define CAMERA_MODEL_M5STACK_PSRAM//#define CAMERA_MODEL_M5STACK_WIDE#define CAMERA_MODEL_AI_THINKER#include "camera_pins.h"const char* ssid = "Your wifi name";const char* password = "wifi password";void startCameraServer();void setup() { Serial.begin(115200); Serial.setDebugOutput(true); Serial.println(); camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = Y2_GPIO_NUM; config.pin_d1 = Y3_GPIO_NUM; config.pin_d2 = Y4_GPIO_NUM; config.pin_d3 = Y5_GPIO_NUM; config.pin_d4 = Y6_GPIO_NUM; config.pin_d5 = Y7_GPIO_NUM; config.pin_d6 = Y8_GPIO_NUM; config.pin_d7 = Y9_GPIO_NUM; config.pin_xclk = XCLK_GPIO_NUM; config.pin_pclk = PCLK_GPIO_NUM; config.pin_vsync = VSYNC_GPIO_NUM; config.pin_href = HREF_GPIO_NUM; config.pin_sscb_sda = SIOD_GPIO_NUM; config.pin_sscb_scl = SIOC_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; //init with high specs to pre-allocate larger buffers if(psramFound()){ config.frame_size = FRAMESIZE_UXGA; config.jpeg_quality = 10; config.fb_count = 2; } else { config.frame_size = FRAMESIZE_SVGA; config.jpeg_quality = 12; config.fb_count = 1; }#if defined(CAMERA_MODEL_ESP_EYE) pinMode(13, INPUT_PULLUP); pinMode(14, INPUT_PULLUP);#endif // camera init esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x", err); return; } sensor_t * s = esp_camera_sensor_get(); //initial sensors are flipped vertically and colors are a bit saturated if (s->id.PID == OV3660_PID) { s->set_vflip(s, 1);//flip it back s->set_brightness(s, 1);//up the blightness just a bit s->set_saturation(s, -2);//lower the saturation } //drop down frame size for higher initial frame rate s->set_framesize(s, FRAMESIZE_QVGA);#if defined(CAMERA_MODEL_M5STACK_WIDE) s->set_vflip(s, 1); s->set_hmirror(s, 1);#endif WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); startCameraServer(); Serial.print("Camera Ready! Use 'http://"); Serial.print(WiFi.localIP()); Serial.println("' to connect");}void loop() { // put your main code here, to run repeatedly: delay(10000);}
步骤4、查看运行结果
工具→串口监视器,然后按下esp32-cam的复位键
复制网址在网页打开,即可看摄像头实时内容了
二、半小时内实现图像识别
1、网页视频流
和前面的Arduino例程相似,但包含的库不是官方库,而是这个:点击这里
下载该zip库后在IDE操作包含该库,然后复制下面代码作为一个新工程ino文件。注意:要配置自己的板件,然后改成自己的wifi和密码
#include "eloquent.h"#include "eloquent/networking/wifi.h"#include "eloquent/vision/camera/esp32/webserver.h"// 把 'm5wide' 替换成自己的模块,// 支持的模块有 'aithinker', 'eye', 'm5stack', 'm5wide', 'wrover'#include "eloquent/vision/camera/aithinker.h"//我用的是aithinkervoid setup() { Serial.begin(115200); delay(2000); camera.jpeg(); camera.qqvga(); // 改成自己的wifi和密码 while (!wifi.connectTo("Abc", "12345678")) Serial.println("Cannot connect to WiFi"); while (!camera.begin()) Serial.println("Cannot connect to camera"); webServer.start(); Serial.print("Camera web server started at http://"); Serial.println(WiFi.localIP());}void loop() { // do nothing}
编译烧到esp32-cam板子上后打开串口监视器,获取网址(我的是192.168.1.103),然后在网页打开即可,和常规Arduino的视频流例程差不多。
视频窗口设这么小是为了让视频更加流畅。
2、通过视频流采集目标并训练
训练环境是Python,我这边推荐Anaconda
简单介绍就是:数据可视化+JupyterNotebook+Spyder
下载用不了多长时间的,然后我们只需要用其中的IDE:Spyder
下载好之后安装everywhereml包,打开Anaconda Powershell Prompt输入以下指令,already表示包已经安装好了
pip install everywhereml>=0.2.19
步骤1、新建Spyder工程
project->new project
然后把学习训练模型的Python工程解压添加到工程里,点击获取Python工程
然后打开Spyder软件如图显示,左边工程文件栏目里就会显示Python工程,此外派上用场的还有交互界面和数据可视化显示界面
步骤2、训练数据获取
复制以下代码到交互界面并回车,从视频流中截取目标图片作为模型数据支撑
from logging import basicConfig, INFOfrom everywhereml.data import ImageDatasetfrom everywhereml.data.collect import MjpegCollector# 给将要存放数据的文件夹命名base_folder = 'Images_Data'# 视频流显示的那个网页地址IP_ADDRESS_OF_ESP = 'http://192.168.1.103'basicConfig(level=INFO)try: image_dataset = ImageDataset.from_nested_folders( name='Dataset', base_folder=base_folder )except FileNotFoundError: mjpeg_collector = MjpegCollector(address=IP_ADDRESS_OF_ESP) image_dataset = mjpeg_collector.collect_many_classes( dataset_name='Dataset', base_folder=base_folder, duration=30 )print(image_dataset)
然后就会弹出让你给创建的类命名,我先什么都不识别所以命名none然后回车,如下图所示
之后会显示提示拍了1272张图作为模型训练基础,并询问该类是否ok
INFO:root:Captured 1272 imagesIs this class ok? (y|n)
接着输入y回车,如果是第一次的话会提示建立文件夹Images_Data存数据
INFO:root:creating D:\Esp_Cam\Spyder_Demo\Esp32_Cam\Images_Data folderINFO:root:creating D:\Esp_Cam\Spyder_Demo\Esp32_Cam\Images_Data\none folderWhich class are you going to capture? (leave empty to exit)
打开对应文件夹就会发现里边存了拍下来的图片数据
同样的,我训练了pen、napkin
如果不想添加了,不用输入直接回车
Which class are you going to capture? (leave empty to exit) Are you sure you want to exit? (y|n)
然后输入y回车退出,这时候就会显示所训练的类
ImageDataset[Dataset](num_images=3704, num_labels=3, labels=['napkin', 'none', 'pen'])
步骤3、数据处理并建立模型
步骤2获取了纸巾、笔、空白的情况下各一千多张图片作为数据支撑
首先对图片进行灰化
在交互界面执行
image_dataset = image_dataset.gray().uint8()
可以在交互界面执行以下代码预览数据处理情况
image_dataset.preview(samples_per_class=10, rows_per_class=2, figsize=(20, 10), cmap='gray')
然后使用定向梯度直方图算法进行处理
定向梯度直方图( Histogram of Oriented Gradients,简称HOG),该算法是轻量级的很适合Esp32-cam使用。
在交互界面执行以下代码
from everywhereml.preprocessing.image.object_detection import HogPipelinefrom everywhereml.preprocessing.image.transform import Resizepipeline = HogPipeline( transforms=[ Resize(width=40, height=30)#此处的分辨率会影响处理时间和模型建立的准确度,可自行调整 ])feature_dataset = pipeline.fit_transform(image_dataset)feature_dataset.describe()
接着输出由特征向量组成的数据集
print(pipeline)
如果想看所提取的特征量信息情况,可以绘制配对图(pairplot)直观感受数据
feature_dataset.plot.features_pairplot(n=200, k=8)
可以直观的看到,这3个类(none、napkin、pen)的聚集性质良好,但在某种程度上彼此是有混合的情况。
使用降维算法进一步优化
使用的降维算法是统一流形逼近与投影(Uniform Manifold Approximation and Projection,简称UMAP)
feature_dataset.plot.umap()
分析点聚集性质可知,1(none)的模型最理想,0(napkin)和2(pen)的模型相对比较差。
总的来说,也算是能够用来表征我们的数据了。
最后训练分类器完成模型建立
使用的建模方法叫随机森林(Random Forest,简称RF)
from everywhereml.sklearn.ensemble import RandomForestClassifierfor i in range(10): clf = RandomForestClassifier(n_estimators=5, max_depth=10) train, test = feature_dataset.split(test_size=0.4, random_state=i) clf.fit(train) print('Score on test set: %.2f' % clf.score(test))clf.fit(feature_dataset)
现在,我们已经训练并且建好模型了
3、生成代码移植到Esp32-Cam
(1)将HOG和RF算法转换为可以在 Esp32-cam 上运行的C++代码
HOG算法获取特征向量数据集
print(pipeline.to_arduino_file( filename='path-to-sketch/HogPipeline.h', instance_name='hog'))
RF算法训练分类器
print(clf.to_arduino_file( filename='path-to-sketch/HogClassifier.h', instance_name='classifier', class_map=feature_dataset.class_map))
这时候就会生成两个.h文件在path-to-sketch/ 目录下
(2)创建Arduino项目工程
ino文件里替换成以下代码
#include "eloquent.h"#include "eloquent/print.h"#include "eloquent/tinyml/voting/quorum.h"// 支撑的有 'aithinker', 'eye', 'm5stack', 'm5wide', 'wrover'#include "eloquent/vision/camera/aithinker.h"//我用的是aithinker#include "HogPipeline.h"//Spyder里生成的#include "HogClassifier.h"//Spyder里生成的Eloquent::TinyML::Voting::Quorum<7> quorum;void setup() { Serial.begin(115200); delay(3000); Serial.println("Begin"); camera.qqvga(); camera.grayscale(); while (!camera.begin()) Serial.println("Cannot init camera");}void loop() { if (!camera.capture()) { Serial.println(camera.getErrorMessage()); delay(1000); return; } hog.transform(camera.buffer); uint8_t prediction = classifier.predict(hog.features); int8_t stablePrediction = quorum.vote(prediction); if (quorum.isStable()) { eloquent::print::printf( Serial, "Stable prediction: %s \t(DSP: %d ms, Classifier: %d us)\n", classifier.getLabelOf(stablePrediction), hog.latencyInMillis(), classifier.latencyInMicros() ); } camera.free();}
找到前面生产的两个.h文件,然后包含进工程里(把两个.h文件复制到工程里边)