我们打算做一款帮助盲人“看到”世界的 AI 硬件。
这个想法的起因是某天中午下班,笔者正在路上走着,忽听到后面传来很轻的歌声。回头看,一个戴着墨镜的男人拿着盲杖,正摸索着沿盲道前行。生活中很少遇到盲人,于是特意留意了下。他立得很直,嘴巴轻轻开合发出歌声,虽然走得慢,却每一步都很扎实。笔者和他一起走了一段,看到前方的盲道上停了一辆摩托车。走在我前面另一个女生尝试去搬走摩托车,见状我也快步上前搭了把手,一起移开了它。因为赶时间,移开车后我就走开了,歌声也越来越远。
后来我想,假如没有人把车移开,就算有盲杖探路,可能也会给这位朋友造成一些不大不小的困扰吧。有什么办法可以提前告知他前方的路况吗?巧的是,多模态大模型能通过自然语言对话、理解图像,理论上正好能充当这位朋友的眼睛。
于是有了这个产品的尝试。
按设想,用户交互的流程是佩戴硬件->摄像头拍摄物理世界发给大模型->大模型理解后以语音形式告知用户前方的路况信息。硬件不可少的模块包括:摄像头、麦克风,网络连接先用WIFI,另外可能需要蓝牙连接手机。
选择的开发板是 XIAO ESP32 S3,笔者是 ESP32 开发的纯新人,故而拿到硬件之后先测试各个模块的功能,同时学习开发过程。
开发板真容如下:
测试代码直接选择的官方示例,地址: https://wiki.seeedstudio.com/cn/xiao_esp32s3_camera_usage/
测试摄像头
下方仅摘抄主要流程代码并在其中加上注释,可视作伪代码理解流程。
void photo_save(const char * fileName) { // 调用摄像头拍摄一帧画面 camera_fb_t *fb = esp_camera_fb_get(); // 保存图像 writeFile(SD, fileName, fb->buf, fb->len); // 释放图像 buffer esp_camera_fb_return(fb);}// 图像存储到 SD 卡void writeFile(fs::FS &fs, const char * path, uint8_t * data, size_t len){ File file = fs.open(path, FILE_WRITE); file.close();}// ESP32 的初始化代码void setup() { // 初始化串口 Serial.begin(115200); // 初始化相机 esp_err_t err = esp_camera_init(&config);}// ESP32 的循环程序入口void loop() { char filename[32]; sprintf(filename, "/image1.jpg"); photo_save(filename); Serial.printf("Saved picture: %s\r\n", filename);}
上面的代码执行完毕会在 SD 卡中保存摄像头拍摄的画面,我们在其中找到下图,是笔者电脑屏幕正播放的内容,摄像头功能正常。
测试麦克风
void setup() { Serial.begin(115200); // 设置 I2S 接口使用的引脚,并初始化 I2S 接口 I2S.setAllPins(-1, 42, 41, -1, -1); I2S.begin(PDM_MONO_MODE, SAMPLE_RATE, SAMPLE_BITS) // 麦克风开始录制音频 record_wav();}void loop() { // 不执行复杂逻辑}void record_wav(){ // 打开要保存的音频文件 File file = SD.open("/"WAV_FILE_NAME".wav", FILE_WRITE); // 生成 wav 格式文件的头信息 generate_wav_header(wav_header, record_size, SAMPLE_RATE); // 往 wav 文件写头信息 file.write(wav_header, WAV_HEADER_SIZE); // 开始记录音频 esp_i2s::i2s_read(esp_i2s::I2S_NUM_0, rec_buffer, record_size, &sample_size, portMAX_DELAY); // 往 wav 文件保存音频 file.write(rec_buffer, record_size) // 释放 buffer free(rec_buffer); file.close();}
上面代码执行完毕后,会在 SD 卡中保存麦克风录制的 20 秒音频文件,我们看到确实保存了,且播放正常。
测试蓝牙
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str()); }};void setup() { Serial.begin(115200); Serial.println("Scanning..."); BLEDevice::init(""); pBLEScan = BLEDevice::getScan(); pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); pBLEScan->setActiveScan(true); pBLEScan->setInterval(100); pBLEScan->setWindow(99);}void loop() { // 开始扫描蓝牙信号 BLEScanResults foundDevices = pBLEScan->start(scanTime, false); Serial.print("Devices found: "); Serial.println(foundDevices.getCount()); Serial.println("Scan done!"); pBLEScan->clearResults(); delay(10000);}
上述代码执行完毕会列出附近扫描到的蓝牙信号,打开 Serial Monitor,可以看到正常扫描得到了结果:
测试 WIFI
void setup() { Serial.begin(115200);}void loop() { // 开始扫描 WIFI 信号,返回附近的 WIFI 数量 int n = WiFi.scanNetworks(); Serial.println("scan done"); if (n == 0) { Serial.println("no networks found"); } else { Serial.print(n); Serial.println(" networks found"); for (int i = 0; i < n; ++i) { // 打印 WIFI 信息 Serial.print(i + 1); Serial.print(": "); Serial.print(WiFi.SSID(i)); Serial.print(" ("); Serial.print(WiFi.RSSI(i)); Serial.print(")"); Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN)?" ":"*"); delay(10); } } Serial.println(""); // 延迟 5s 进行下一次扫描 delay(5000);}
上述代码运行完毕会列出周围 WIFI 的信息,实际结果显示工作正常:
测试网络连接
void setup() { USE_SERIAL.begin(115200); wifiMulti.addAP("MY WIFI SSID", "PASSWORD");}void loop() { // 当 WIFI 连接上 if((wifiMulti.run() == WL_CONNECTED)) { // 请求测试网页 http.begin("http://example.com/index.html"); int httpCode = http.GET(); // 成功返回结果 if(httpCode == HTTP_CODE_OK) { String payload = http.getString(); USE_SERIAL.println(payload); } http.end(); } delay(5000);}
上述代码将请求测试网页,并返回测试网页的代码。从测试结果看功能正常:
测试大模型
最后,我们测试大模型是否能为盲人朋友识别路况,以最简单的图片中盲道被占的图片为例:
我们用 GPT-4o 来进行测试,结果如下:
我们看到大模型将道路中的摩托车、盲道上的铁架和石块都识别出来了,这是影响盲道畅通的关键因素。当然其中还有一些元素识别有问题,作为后续改进的项目。
总结:
本文对购买的 XIAO ESP32 S3 开发板,测试了摄像头、麦克风、蓝牙、WIFI、网络连接以及最后的大模型识别能力,发现基本功能满足需求。后续将以此为基础,进行该硬件的开发。
欢迎关注哦~
希望各位能提提意见,比如这个想法可行性、实用性,或者其它帮助完善的其它想法都可以的。