跳转到内容

Base64 图像编码

Base64 图像编码

本节介绍如何将 ESP32-CAM 拍摄的图片编码为 Base64 格式,以便在 JSON 消息中传输。学习完成后,您将能够:

  • 理解 Base64 编码在 IoT 中的应用场景
  • 实现 ESP32 端的 Base64 图片编码
  • 在 Node-RED 中解码和显示 Base64 图片
  • 评估 Base64 传输的优缺点

在 IoT 场景中,Base64 编码的主要用途:

二进制传输:
[MQTT 二进制 Payload]
优点: 数据量小 (原始 JPEG ≈ 30KB)
缺点: 需要特殊处理,调试不直观
Base64 JSON 传输:
[MQTT JSON Payload]
{
"device": "esp32cam-01",
"image": "/9j/4AAQ...", // Base64
"timestamp": 1694321234
}
优点: 可包含元数据,易于调试
缺点: 数据增大 ~33%
#include <WiFi.h>
#include <PubSubClient.h>
#include "esp_camera.h"
#include "base64.h" // ESP32 内置 base64 库
// 拍照并返回 Base64 编码的字符串
String takePhotoBase64() {
camera_fb_t* fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Camera capture failed!");
return "";
}
Serial.printf("Original JPEG size: %zu bytes\n", fb->len);
// Base64 编码
// 输出大小 ≈ 输入大小 × 4/3
String base64Image = base64::encode(fb->buf, fb->len);
Serial.printf("Base64 encoded size: %d bytes\n", base64Image.length());
Serial.printf("Overhead: %.1f%%\n",
(float)(base64Image.length() - fb->len) / fb->len * 100);
esp_camera_fb_return(fb);
return base64Image;
}
// JSON 格式发送 Base64 图片
void sendPhotoJSON() {
String base64Img = takePhotoBase64();
if (base64Img.length() == 0) return;
// 构建 JSON 消息
String json = "{\"device\":\"esp32cam-01\",";
json += "\"timestamp\":" + String(millis()) + ",";
json += "\"width\":640,";
json += "\"height\":480,";
json += "\"format\":\"jpeg\",";
json += "\"size\":" + String(base64Img.length()) + ",";
json += "\"image\":\"" + base64Img + "\"}";
Serial.printf("JSON message size: %d bytes\n", json.length());
// 通过 MQTT 发送 JSON
if (client.connected()) {
bool sent = client.publish("esp32cam/photo_json", json.c_str());
if (sent) {
Serial.println("Base64 photo sent via MQTT");
}
}
}

Base64 编码需要大量内存:

// VGA 640×480 JPEG 大约 30-50KB
// Base64 编码后大约 40-68KB
// JSON 包装后大约 42-70KB
// 内存需求计算:
// 原始图片: ~50KB (fb->buf)
// Base64 输出: ~68KB (编码后字符串)
// JSON 消息: ~70KB (完整消息)
// 总内存需求: ~120KB+ (需要 PSRAM)
// 内存优化: 分块编码
void sendPhotoChunked() {
camera_fb_t* fb = esp_camera_fb_get();
if (!fb) return;
int chunkSize = 1024; // 1KB 每块
int totalChunks = (fb->len + chunkSize - 1) / chunkSize;
for (int i = 0; i < totalChunks; i++) {
int offset = i * chunkSize;
int size = min(chunkSize, (int)(fb->len - offset));
String encoded = base64::encode(fb->buf + offset, size);
// 发送每个块
String json = "{\"chunk\":" + String(i) + ",";
json += "\"total\":" + String(totalChunks) + ",";
json += "\"data\":\"" + encoded + "\"}";
client.publish("esp32cam/photo_chunk", json.c_str());
delay(10); // 避免 MQTT 缓冲溢出
}
esp_camera_fb_return(fb);
}
[MQTT In: esp32cam/photo_json] ──→ [Function: 解码] ──→ [Image Output]
// Function: 解码 Base64 图片
var data = msg.payload;
if (typeof data === 'string') {
data = JSON.parse(data);
}
// 提取 Base64 图片数据
var base64Data = data.image;
var deviceId = data.device || "unknown";
var timestamp = data.timestamp || Date.now();
// 解码 Base64 为 Buffer
var imageBuffer = Buffer.from(base64Data, 'base64');
// 设置输出
msg.payload = imageBuffer; // 二进制图片数据
// 添加元数据
msg.image = {
device: deviceId,
timestamp: timestamp,
width: data.width,
height: data.height,
size: imageBuffer.length
};
return msg;
// Function: 保存图片到服务器文件系统
var data = msg.payload;
var base64Data = data.image;
var deviceId = data.device || "esp32cam-01";
// 生成文件名
var now = new Date();
var filename = "/data/photos/" + deviceId + "_" +
now.getFullYear() + "-" +
String(now.getMonth()+1).padStart(2,'0') + "-" +
String(now.getDate()).padStart(2,'0') + "_" +
String(now.getHours()).padStart(2,'0') +
String(now.getMinutes()).padStart(2,'0') +
String(now.getSeconds()).padStart(2,'0') + ".jpg";
// 解码并保存
var imageBuffer = Buffer.from(base64Data, 'base64');
// 使用 write-file 节点保存
msg.filename = filename;
msg.payload = imageBuffer;
return msg;
特性二进制传输Base64 JSON 传输
数据大小原始 JPEG 大小增加 ~33%
元数据支持需额外的 Topic内嵌在 JSON 中
调试便利性低 (二进制不可读)高 (JSON 可读)
MQTT 兼容性需要二进制支持标准字符串即可
内存占用较低较高 (编码额外开销)
适用场景纯图片传输带元数据的图片
Terminal window
# 查看 Base64 JSON 消息
mosquitto_sub -t "esp32cam/photo_json" -C 1 | head -c 200
# 输出: {"device":"esp32cam-01","timestamp":...,"image":"/9j/4AAQ..."}
# 验证 Base64 解码 (Linux/Mac)
mosquitto_sub -t "esp32cam/photo_json" -C 1 | \
python3 -c "import sys,json; d=json.load(sys.stdin); open('photo.jpg','wb').write(__import__('base64').b64decode(d['image']))"
# 检查解码后的图片
ls -la photo.jpg
file photo.jpg

Q1: Base64 编码的 33% 额外开销是否值得?

Section titled “Q1: Base64 编码的 33% 额外开销是否值得?”

如果只需要图片数据,二进制传输更高效。如果需要同时传输设备 ID、时间戳、GPS 坐标等元数据,Base64 JSON 更方便。

Q2: ESP32 内存够处理 Base64 编码吗?

Section titled “Q2: ESP32 内存够处理 Base64 编码吗?”

需要 PSRAM。VGA 图片编码需要约 120KB 额外内存。如果内存不足,建议使用二进制传输或在 Node-RED 端进行格式转换。

Q3: 分块编码会影响图片完整性吗?

Section titled “Q3: 分块编码会影响图片完整性吗?”

每个块独立编码,在 Node-RED 端按顺序拼接解码即可恢复完整图片。需要实现块序号检测和超时重传机制。

推荐做法:

  • VGA 以下分辨率使用 Base64,UXGA 使用二进制
  • 确保 ESP32 配置了 PSRAM
  • 大图片 (>100KB) 使用分块传输
  • 在 Node-RED 端验证 Base64 解码完整性

避免做法:

  • 无 PSRAM 时对大图片进行 Base64 编码
  • 单条 MQTT 消息超过 128KB
  • 分块传输无序号和完整性校验
  • Base64 字符串中包含非法字符
  1. Base64 将二进制图片编码为文本,增大 ~33%
  2. JSON + Base64 支持图片和元数据一起传输
  3. ESP32 Base64 编码需要 PSRAM 支持大图片
  4. 分块编码可解决大图片内存不足问题
  5. Node-RED 使用 Buffer.from(data, 'base64') 解码