news 2026/10/5 5:21:46

ESP32-CAM+Gemini API:自制智能垃圾分类垃圾桶实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESP32-CAM+Gemini API:自制智能垃圾分类垃圾桶实战

我家的垃圾桶一直是个纠结点:每次丢垃圾都要在手机里查“这玩意到底是可回收还是厨余”,查完还要纠结一轮。后来我手头刚好剩下一块ESP32-CAM,就动了心思——能不能把它装在垃圾桶上方,配合Google的Gemini大模型做一次实时识别,让垃圾桶自己判断手里拿的是什么,然后给出分类结果甚至自动分拣?这个项目最终跑通了,识别一次大概3到6秒,对常见的果蔬、塑料瓶、纸张、电池等生活垃圾判断得还挺稳。整套方案的关键就一句话:ESP32-CAM负责拍照,Gemini API负责“看懂”照片,舵机负责执行分类动作。这篇文章把从硬件搭建、API接入到整机联动的完整过程都写下来,适合想做智能家居小项目、刚接触ESP32-CAM或者想试试“大模型+嵌入式硬件”这种玩法的朋友参考。

1. 系统思路:一块摄像头开发板为什么敢接云端大模型

1.1 三个环节拆解

这个智能垃圾桶本质上是一个“感知—推理—执行”的闭环。感知层用ESP32-CAM自带的OV2640摄像头拍照,推理层把图片上传到Gemini API让大模型识别垃圾类型,执行层用舵机翻开对应分类的隔板或者点亮不同颜色的指示灯。

很多人的第一反应是:一块几十块钱的开发板,跑得动AI模型吗?答案是跑不动,也不需要跑。Gemini这类云端大模型负责的只是“看图说话”,ESP32-CAM要做的只是把一张JPEG图片通过Wi-Fi发出去,再解析返回的JSON结果。开发板真正消耗资源的地方,反而是拍照、Base64编码和网络请求这三个看起来不起眼的环节。

这个分工其实特别像“点菜”的场景:ESP32-CAM是传菜的店员,负责拍菜单、送厨房;Gemini是大厨,负责辨认食材并给出做法;舵机是上菜的服务生,按大厨的指示把菜送到对应桌上。每个角色做自己擅长的事,系统才能稳定。

1.2 为什么不用端侧识别

做垃圾分类识别,很多人会先想到端侧AI方案,比如用Edge Impulse训练一个轻量级图像分类模型,直接在ESP32上跑。这条路我以前也试过,训练一个能区分“矿泉水瓶、易拉罐、纸巾、香蕉皮”的小模型就花了整整一个周末——拍样本、标注、训练、量化、部署,每一步都在折腾。

更要命的是泛化能力:模型只认训练过的物体,换一种包装、换个光源,准确率立刻掉下来。家里的垃圾五花八门,你不可能给每种垃圾都拍几百张图做训练集。Gemini这样的通用多模态大模型恰好解决了这个问题,它对常见物品的认知来自海量预训练数据,不需要我自己准备样本,零样本也能识别。代价就是必须联网、有一定延迟,而且每次调用都消耗API配额。

所以我最终的方案是:把大模型的强泛化能力用在“识别”上,把轻量逻辑留在本地。这也是为什么说“AI垃圾桶”听起来玄乎,实际落地反而比想象中的端侧AI方案简单。

1.3 选型:Flash模型、结构化输出与免费额度

Gemini API里我选了gemini-1.5-flash这个模型。Flash系列本身定位就是“快速、低成本、适合高并发任务”,对单张图片的识别延迟比Pro模型低一大截。我这边的实测数据是:纯模型推理时间一般在1到3秒,加上图片上传和网络往返,总共3到6秒。如果你现在开通的是新版本,也可以把模型名换成gemini-2.0-flash,调用格式完全一样。

比选模型更重要的是设置结构化输出。我让Gemini返回一个JSON对象,包含垃圾类别、物品名称、置信度和投放建议,而不是让它自由发挥。这样解析结果的时候就不需要处理一大堆自然语言,直接读取字段就能驱动舵机。

配额方面,Gemini API的免费层对个人调试非常友好,日常测试基本不会撞到额度上限。不过为了防止误触发导致的高频调用,我在代码里加了最小识别间隔,这个后面会细说。

2. 硬件搭建:ESP32-CAM最小分拣装置

2.1 器件与成本清单

这个项目的硬件清单非常短,大部分零件我都是一次性从常用渠道买的,整套下来不到一百块:

器件用途参考价格
ESP32-CAM(AI Thinker带PSRAM)主控+摄像头30-45元
SG90舵机驱动分类翻板6-10元/个
WS2812B灯环或普通LED分类指示5-15元
5V/2A电源适配器整机供电10-20元
降压模块/面包板/杜邦线连接与调试10-20元
TF卡可选,存识别日志15-25元

我强烈建议买带PSRAM的ESP32-CAM版本,后面讲内存的时候你会理解为什么。另外垃圾桶壳体我用纸板加热熔胶搭了个临时结构,能固定摄像头和舵机就行,不用太讲究。

2.2 接线顺序与供电细节

接线看起来简单,但这个板子有几个坑值得单独说一下。先看引脚规划:

  • 摄像头信号线:开发板已经固定接到内部引脚,不需要你操心;
  • 舵机信号线:接GPIO14;
  • 补光LED:接GPIO4(板上自带的闪光灯焊盘就在这);
  • 指示灯:接GPIO12(建议串一个220Ω电阻);
  • 触发按钮或超声波传感器:接GPIO13。

注意GPIO12是MTDI引脚,上电时序比较敏感。如果你手头的模块在GPIO12上接了设备后出现启动异常,别怀疑程序,先把GPIO12的器件拆掉再看看。舵机供电不要走开发板的3.3V引脚,SG90启动瞬间电流能到500mA以上,会把板载稳压直接拖垮。正确做法是舵机正负极单独接5V电源,信号线接GPIO14,同时把舵机电源的GND和ESP32-CAM的GND共地。系统里所有模块要有共同的电位参考点,否则信号会乱跳。

2.3 一个容易忽视的前提:PSRAM

ESP32-CAM有两个版本,带8MB PSRAM和不带PSRAM的。别买不带PSRAM的版本。拍一张VGA分辨率的JPEG照片可能只有30到60KB,但Base64编码之后字符串要膨胀约33%,再加上JSON请求体、响应缓冲区,Arduino环境里如果没有外部RAM,很容易直接崩掉或者反复重启。

我在代码里用psramFound()做了一次检测:

if (!psramFound()) { Serial.println("PSRAM not found, please use ESP32-CAM with PSRAM!"); }

另外摄像头库会默认尝试把帧缓冲分配到PSRAM里。没有PSRAM的版本在上电初始化时就会报malloc failed,这几乎是ESP32-CAM项目最常见的启动失败原因。

3. 从拍照到调用Gemini:核心链路实现

3.1 摄像头初始化参数

开发环境我用的是Arduino IDE加esp32-camera库,选好“AI Thinker ESP32-CAM”开发板型号后,初始化代码可以直接从示例里改。关键是这几项参数:

#include "esp_camera.h" 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_sccb_sda = SIOD_GPIO_NUM; config.pin_sccb_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; config.frame_size = FRAMESIZE_VGA; config.jpeg_quality = 12; config.fb_count = 1;

VGA分辨率(640x480)是我反复试下来最均衡的选择。再高比如UXGA,JPEG文件能到一两百KB,Base64之后请求体太大,上传时间翻倍,而且识别准确率提升非常有限。质量参数不建议低于10,太低会让画面出现明显的压缩块,Gemini识别小字或者小物体时会吃力。

3.2 图片转Base64与内存管理

Gemini API接受两种图片传入方式:图片URL和Base64内联数据。ESP32-CAM拍出来的照片没有公网URL,所以必须转成Base64字符串塞进JSON里。

拍照拿到的是camera_fb_t结构体,里面直接就是JPEG二进制数据。转Base64我用的是ESP-IDF自带的mbedtls函数,不需要额外装库:

#include "mbedtls/base64.h" camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { Serial.println("Camera capture failed"); return; } size_t b64_len = 4 * ((fb->len + 2) / 3) + 1; char *b64 = (char *)ps_malloc(b64_len); if (!b64) { esp_camera_fb_return(fb); Serial.println("Failed to allocate base64 buffer"); return; } size_t olen = 0; int ret = mbedtls_base64_encode((unsigned char *)b64, b64_len, &olen, fb->buf, fb->len); esp_camera_fb_return(fb); if (ret != 0) { free(b64); Serial.println("Base64 encode failed"); return; }

这里用ps_malloc而不是普通的malloc,就是为了把大字符串放到PSRAM里,避免吃光内部堆内存。Base64字符串构造完成后,记得在发送完请求之后free(b64),否则多识别几次内存就满了。

3.3 HTTPS POST请求的写法

ESP32-CAM调用Gemini API必须走HTTPS。Arduino的HTTPClient库默认用普通WiFiClient,不能直接请求HTTPS地址,必须换成WiFiClientSecure并关闭证书验证:

#include <WiFi.h> #include <WiFiClientSecure.h> #include <HTTPClient.h> WiFiClientSecure client; client.setInsecure(); HTTPClient http; String url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key="; url += String(GEMINI_API_KEY); http.begin(client, url); http.addHeader("Content-Type", "application/json"); int httpCode = http.POST(payload); String response = http.getString(); http.end();

setInsecure()的意思是跳过TLS证书校验,开发阶段省事,生产环境不建议这么干。进阶做法是用setCACert()加载公网根证书,但ESP32-CAM的内存和Flash空间有限,证书文件管理起来比较麻烦,我目前这个项目就停留在开发阶段,所以一直用的setInsecure()。

这里有个很多人第一次都会踩的坑:直接用HTTPClient访问https://开头的URL,编译能过但运行时一直报连接失败。原因就是没有替换成WiFiClientSecure。记牢这一条就能省下一个下午。

3.4 解析Gemini返回的JSON

请求发出后,Gemini返回的JSON结构是固定的:candidates[0].content.parts[0].text里装着模型生成的文本。由于我在配置里强制了responseMimeType为application/json,这个text字段本身就是一个JSON字符串,所以需要连续解析两次:

DynamicJsonDocument respDoc(16384); deserializeJson(respDoc, response); const char *text = respDoc["candidates"][0]["content"]["parts"][0]["text"]; DynamicJsonDocument resultDoc(2048); deserializeJson(resultDoc, text); const char *category = resultDoc["category"]; const char *name = resultDoc["name"]; float confidence = resultDoc["confidence"];

第一层解析拿到模型输出的文本,第二层解析才拿到我们真正关心的字段。如果你忘了设置结构化输出,text字段就会是一段自然语言描述,解析逻辑会变得非常痛苦。这也是为什么我强烈建议用JSON模式。

4. 提示词与结构化输出:把识别结果变成动作

4.1 提示词设计思路

大模型识别垃圾这件事,提示词写得好不好直接影响返回结果是否稳定。我最终用的提示词大概是这样的:

你是家庭垃圾分类助手。请识别图片中的物品并给出分类。 你必须严格按JSON格式返回,不要输出任何多余内容。 分类枚举值:recyclable(可回收)、kitchen_waste(厨余垃圾)、hazardous(有害垃圾)、other(其他垃圾)、empty(空桶)、unrecognized(无法识别)。 返回字段:category、name(中文名称)、confidence(置信度0-1)、guidance(一句中文投放建议)。

这里有几个设计细节值得说。一是明确给出分类枚举值,让模型在限定集合里选答案,而不是自由发挥,否则它写出“塑料类”“干垃圾”这种描述你就需要再做一层文字匹配。二是加入“空桶”类别,因为垃圾桶经常是空的,摄像头可能对着桶底拍,此时模型应该告诉你“没有垃圾”。三是要求返回中文名称,方便调试时直接看串口日志。

4.2 用responseSchema锁定JSON字段

提示词只能约束,真正硬性锁定输出格式的是API侧的配置。Gemini API支持通过generationConfig传入响应模式:

{ "generationConfig": { "temperature": 0.2, "maxOutputTokens": 1024, "responseMimeType": "application/json", "responseSchema": { "type": "OBJECT", "properties": { "category": { "type": "STRING", "enum": ["recyclable", "kitchen_waste", "hazardous", "other", "empty", "unrecognized"] }, "name": { "type": "STRING" }, "confidence": { "type": "NUMBER" }, "guidance": { "type": "STRING" } }, "required": ["category", "name", "confidence"] } } }

注意temperature要调低,我只用了0.2,避免模型每次都生成不同的分类判断。你可能需要根据自己家的垃圾构成微调枚举值,比如加入“可回收”和“不可回收”的细分。结构化输出的意义不只是方便解析,更重要的是堵死模型“说废话”的路径,不然稍不留神它就会在返回文本里加一句道歉或者解释。

4.3 分类结果到执行动作的映射

拿到category字符串后,下一步就是映射到具体动作。我用一个简单的结构体把类别、舵机角度、LED颜色和提示文案绑定在一起:

struct WasteAction { const char *category; int servo_angle; uint32_t led_color; const char *message; }; WasteAction actions[] = { {"recyclable", 0, 0xFF0000, "可回收物,请投入蓝色桶"}, {"kitchen_waste", 45, 0x00FF00, "厨余垃圾,请投入绿色桶"}, {"hazardous", 90, 0xFFFF00, "有害垃圾,请投入红色桶"}, {"other", 135, 0x808080, "其他垃圾,请投入灰色桶"}, {"empty", -1, 0x000000, "桶内为空,无需操作"}, {"unrecognized", -1, 0x000000, "无法识别,请人工确认"} };

舵机角度需要根据你实际的机械结构去标定,上面这些角度只是示意的映射关系。空桶和无法识别不触发舵机动作,只亮灯提醒。把动作和识别结果解耦的好处是,以后你想加语音播报、加微信通知,或者改成自动分拣,只需要在对应category上增加行为,不需要改识别链路。

5. 分拣执行与整机联动:舵机、指示灯与主循环

5.1 舵机角度与分拣结构

我做的是一版“单舵机双分类”演示结构:垃圾桶中间有一块倾斜的翻板,舵机转动时,翻板往左倒,垃圾滑进可回收仓;往右倒,垃圾滑进厨余仓。如果你要做四分类,可以再加一个舵机做二级分流:第一个舵机决定“是否进入细分通道”,第二个舵机决定“具体进入哪个仓”。

舵机控制用ESP32Servo库,几分钟就能跑通:

#include <ESP32Servo.h> Servo servo; servo.attach(14, 500, 2400); // GPIO14,脉冲范围匹配SG90 servo.write(0); // 左通道 servo.write(45); // 右通道

注意ESP32Servo库和Arduino自带的Servo库有冲突,装库的时候别两个都装。安装之后编译如果报大量重复定义错误,检查是不是残留了旧版Servo库。

机械结构看似不起眼,其实是这个项目里最容易翻车的地方。舵机扭矩只有1.8kg/cm,翻板上如果堆了太多垃圾,或者垃圾卡在中间,舵机会直接堵转。我最后给翻板做了一个倾斜角度,让垃圾能靠重力滑落,舵机只负责改变滑落方向,不负责硬推垃圾,整个系统稳定很多。

5.2 指示灯与反馈状态机

我用了一枚WS2812B灯环做分类指示。识别的category不同,灯环颜色就不同:可回收蓝色、厨余绿色、有害红色、其他灰色。为了让反馈更明确,识别过程中灯环先亮黄色表示“处理中”,识别完成再切换到对应颜色。

这个逻辑用状态机比较好写,避免在loop里堆一堆互相打架的if:

enum SystemState { STANDBY, CAPTURING, RECOGNIZING, ACTING };

standby状态下等待触发信号,触发后进入capturing,拍照后进入recognizing并亮黄色灯,拿到结果后进入acting,执行舵机动作和颜色反馈,最后回到standby。这个状态机代码看起来简单,但实际调试时帮了大忙,尤其是后面加超时保护逻辑的时候,你一眼就能看出系统卡在哪个环节。

5.3 主循环的触发策略与防抖

最常见的触发方式是超声波传感器检测到有人靠近或者有东西投入,但我第一版用的是按键触发——按下按钮才拍照识别。原因是按键的逻辑最稳定,方便我在开发阶段反复测试而不消耗API配额。

实际情况中如果让垃圾桶24小时开着摄像头自动识别,很容易出现误触发:猫走过去触发一次、光线变化触发一次,每触发一次就消耗一次API额度。所以我加了两道保险:一是触发必须有明确的物理信号,二是两次识别之间至少间隔5秒:

unsigned long lastRecognitionTime = 0; const unsigned long minIntervalMs = 5000; void loop() { bool trigger = digitalRead(TRIGGER_PIN) == LOW; if (trigger && !busy && millis() - lastRecognitionTime > minIntervalMs) { busy = true; lastRecognitionTime = millis(); runRecognitionCycle(); busy = false; } }

这里的runRecognitionCycle封装了拍照、编码、网络请求、解析、执行动作的完整流程。如果某一步失败就立刻返回错误状态并亮红色灯,而不是卡在那里等待,整个系统的容错性会好很多。

5.4 本地日志:把每一次识别都记录下来

调试阶段你会发现,光靠串口监视器看日志有时候不够——你没法回溯“刚才那次识别到底返回了什么”。我把识别结果追加写到TF卡上的CSV文件里,每次触发记录时间戳、category、name、confidence:

File logFile = SD.open("/log.csv", FILE_APPEND); if (logFile) { logFile.printf("%s,%s,%s,%.2f\n", timestamp, category, name, confidence); logFile.close(); }

TF卡用SPI接口挂在SD卡槽上,和摄像头共用部分引脚。这个功能第一版可以不做,但一旦开始反复调参,你就知道有日志到底多珍贵。上周我连续识别了几十次,统计后发现易拉罐和铝箔盒经常被混淆,后来加了一句“注意薄铝制品”的提示词,准确率立刻就上来了——没有日志,你根本找不到这种规律。

6. 实际运行中的坑和排查记录

6.1 403/400/429不是玄学:先按顺序排掉基本错误

很多人在调Gemini API时被错误码劝退,我一开始也遇到过。这里把最常见的几个错误码按经验列一下:

错误码最常见原因排查方式
400请求JSON格式错误、图片Base64不完整检查请求体结构,确认inlineData字段名和大小写正确
403API key无效、未启用、网络不可达先在电脑上用curl验证key和网络,再查板子
429配额超限降低调用频率,加退避重试,检查Google AI Studio配额页
503/504服务端过载或请求体过大降低图片分辨率,增大HTTP读取超时时间

403还有一个容易让人误判的情况:你在网页版或者命令行工具里看到“your account is not eligible for gemini code assist for individuals at this time”这类提示,那是另一套产品(Gemini Code Assist个人版)的资格限制,跟你调用普通Gemini API是完全不相干的。遇到API的403,老老实实检查API key有没有复制完整、有没有在URL里正确传参,比换账号有用得多。

我在电脑上先用curl验证API key没问题,然后再烧到板子里调试。这一步看起来多此一举,实际能帮你区分“网络问题”“API问题”和“板子代码问题”三种情况:

curl -X POST \ "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"contents":[{"parts":[{"text":"say ok"}]}]}'

如果这条命令在电脑上能正常返回,说明key和网络路径都没问题;如果一直卡住或者403,那问题在更上游,别急着改板子代码。

6.2 识别不准:对焦、光线和拍摄角度

OV2640是一颗定焦镜头,出厂对焦距离通常在1米左右。装在垃圾桶上方,摄像头离垃圾可能只有20到30厘米,拍出来的照片会发虚。解决方法是旋转镜头上的对焦环,调到近距离清晰为止。这个操作很精细,最好对着电脑屏幕上的文字边调边看,调到文字边缘最锐利的位置停手。

光线的影响比很多人想象的大。白天靠窗户光倒还好,晚上厨房灯光不足时,识别准确率明显下降,暗光下照片噪点特别多。我在桶盖上加了补光灯,用GPIO4控制板载LED或者外接一个白光LED,拍照前先点亮200ms再抓帧,亮度起来后准确率提升非常明显。还有一个小技巧:垃圾桶内壁最好用纯白或者纯黑的背景,垃圾放在中间,减少背景杂物对模型的干扰。

6.3 网络与超时:先把curl跑通,再谈板子

这是我调试过程中最熬人的一个阶段。ESP32-CAM连上Wi-Fi后,请求发出去经常卡在http.POST这一步,一等就是几十秒,然后返回-1。翻来覆去检查代码逻辑,其实问题出在网络环境对Google API域名的可达性上。

我的经验是:先确认运行环境能正常访问generativelanguage.googleapis.com这个域名。可以在电脑上ping和curl,判断链路是否通畅、延迟大概多少。如果你的局域网、公司网络或者运营商网络对这类域名有额外限制,那就把它当网络可达性问题来考虑,换一个可以正常访问该API的网络环境后再测板子。ESP32的Wi-Fi信号强度也值得检查,拿另一只手机开热点对比一下,往往很快能定位是不是路由器防火墙的问题。

HTTP客户端的读超时默认是15秒,而Gemini推理最快也要1到3秒,加上图片上传和网络抖动,15秒其实是够用的,但为了保险我把readTimeout调到了60秒:

client.setTimeout(60);

6.4 供电不稳导致的舵机抖动与重启

舵机一转动,开发板就重启,这是我这个项目拍过的最长的坑。SG90堵转或者启动瞬间电流过大,会把USB口的电压拉低,ESP32一旦欠压立刻重启。解决办法是把舵机的5V电源从USB取电里拆出来,单独用一个5V/2A的适配器供电,并且就在舵机电源附近并联一个470μF电解电容吸收尖峰。

另外一个细节:ESP32-CAM本身功耗不低,尤其Wi-Fi传输时瞬间电流能到300mA以上。USB线要选粗一点、短一点的,劣质细线压降非常明显。用电池供电时还要注意电池电压掉到4.8V以下,摄像头初始化就会开始不稳定。稳定的供电是这个项目能连续运行的前提,宁可多花十块钱买个好电源,也别在这里省。

6.5 避免烧配额:加一个本地粗筛

在加入超声波触发之前,我试过让垃圾桶每隔几秒自动拍一张识别一次——既不环保又费API。而且让摄像头怼着一堆静态垃圾拍,返回的结果完全一样,纯粹浪费配额。后来我加了超声波测距模块,检测到桶口上方有物体靠近才触发识别。这个本地粗筛的逻辑很简单,却能把API调用次数降到原来的几十分之一,也避免了猫路过引起的“幽灵触发”。

7. 总成本、使用收益与后续可做的事

7.1 一版成本表

整套设备我最终花了大约110元,如果你手头已经有ESP32-CAM、舵机和电源这些常用配件,实际新增支出可能只要二三十块。具体账目如下:

项目支出
ESP32-CAM带PSRAM38元
SG90舵机8元
WS2812B灯环12元
HC-SR04超声波模块6元
电机驱动翻板的支架/纸板/热熔胶15元
5V/2A电源适配器18元
杜邦线、电阻、电容等15元

相比买一台成品智能分类垃圾桶几百上千块的价格,这个DIY方案的好处其实不是省钱,而是所有逻辑都握在自己手里:你想加什么分类、想调整识别灵敏度、想接Home Assistant,都能自己改。

7.2 可以继续扩展的功能

这个项目的扩展空间比我预想的大得多。识别结果既然已经进了系统,你可以往三个方向继续做:一是接一个合成语音模块,让垃圾桶直接“说话”,提示你该扔到哪个桶;二是把结果通过MQTT上报到家里的智能中枢,统计一周产生了多少可回收物,或者提醒你垃圾袋满了;三是建立自己的小型样本库,把每次识别结果和人工修正记录存下来,等积累了数据之后,训练一个只针对自家垃圾场景的端侧小模型,降低对云端的依赖。

我现在最新的一版加入了连续识别校准:当模型置信度低于0.6时,垃圾桶会亮黄灯并等待二次确认,不会急着打开翻板。这个改动让误开仓的概率明显下降,也让我对“AI执行决策”这件事多了一层信任。

7.3 给准备复刻的人三个建议

最后一节写点实在的。第一,别一上来就做四分类自动分拣,先把“拍照—识别—反馈”这条链路跑通,把提示词和API调用的部分调稳定,再考虑舵机和机械结构。第二,开发阶段把API key存在代码里没问题,但做出来给别人用之前,一定要想办法把key放到配置端或者服务端中转,避免密钥泄露后被别人刷额度。第三,遇到问题先从网络和供电查起,我见过太多人花好几个小时debug代码,最后发现是USB线不行或者路由器拦截了域名。

做这个项目给我最大的感受是:大模型真正走进生活,不一定要靠什么复杂的机器人平台,一块几十块的摄像头开发板加一个云端API,就能组合出很实用的工具。技术门槛没有想象中高,但每一个细节——补光、触发逻辑、结构化输出——都决定了它到底是“玩具”还是“工具”。希望这篇记录能帮你少踩几个我踩过的坑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 5:21:37

DCGAN红外图像增强实战:低对比度图像细节重塑全流程

简介&#xff1a;面向红外图像增强与深度学习研究者的完整项目源码&#xff0c;基于DCGAN实现低对比度红外图像增强。针对红外成像对比度低、目标轮廓模糊等痛点&#xff0c;利用生成对抗网络自动学习图像特征&#xff0c;有效改善细节表现。压缩包共16个文件&#xff0c;约21.…

作者头像 李华
网站建设 2026/10/5 5:20:57

V-REP仿真多车道巡线小车:视觉识别与PID避障算法实践

做移动机器人算法验证&#xff0c;仿真环境真的能省掉一大半的烦恼。V-REP&#xff08;2020年后改名为 CoppeliaSim&#xff09;是我用下来比较顺手的一款&#xff0c;物理引擎、传感器仿真和 API 生态都够扎实&#xff0c;做轮式机器人、机械臂、多机协同都没问题。这次分享一…

作者头像 李华
网站建设 2026/10/5 5:20:49

一张图加一段音频生成数字人:从原理到实操的完整指南

数字人这个方向我前前后后折腾了快两年&#xff0c;从最早用现成SaaS工具套模板&#xff0c;到后来自己搭管线跑模型&#xff0c;踩过的坑能写满一个笔记本。最近半年&#xff0c;圈子里讨论最多的就是“一张图加一段音频直接出片”的方案——不需要3D建模&#xff0c;不需要动…

作者头像 李华
网站建设 2026/10/5 5:20:49

二维数组子数组筛选全解析:C#与C语言实现及边界避坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/5 5:20:18

FMCW毫米波雷达原理详解:测距测速、FFT与参数设计

做雷达感知的人&#xff0c;应该都对 FMCW 这三个字母不陌生。FMCW&#xff08;Frequency Modulated Continuous Wave&#xff0c;线性调频连续波&#xff09;是毫米波雷达最常用的信号体制&#xff0c;从 24GHz 的工业测距模块到 77GHz 的车载前向雷达&#xff0c;核心原理其实…

作者头像 李华
网站建设 2026/10/5 5:20:00

大模型API统一管理实战:AI网关架构设计与落地

1. 多模型接入的乱局&#xff1a;为什么统一管理成了刚需我最早接触多模型接入是在两年前&#xff0c;当时团队同时用着三家厂商的对话模型、两家的向量模型&#xff0c;还有一套自部署的开源模型。每个项目组各自申请密钥、各自记账、各自写调用代码&#xff0c;结果就是月底对…

作者头像 李华