一、项目介绍
不知道你在生活中有没有注意到过这样的场景,当我们在快递寄件点,经常会出现工作人员把快递包裹放到电子秤上称重,再拿尺子分别测量长、宽、高,最后根据尺寸和重量判断计费重量。如果寄件高峰期连续来了很多包裹,反复进行“称重—测量—计算”不仅耗时,也容易因为人工读数产生误差。于是我想,能不能把这些步骤集中到一台设备上?工作人员只需要把包裹放到检测平台上,设备就能自动获取包裹尺寸和重量,并把结果用于后续计费重量和运费估算。
本项目以行空板 K10为主控,在包裹上方安装一个矩阵激光测距传感器,利用 64 个测距区域判断包裹的位置和轮廓,再通过背景差分和几何换算得到包裹的长、宽、高;再在平台下方加一个重量传感器,负责获取实际重量。最终,系统将尺寸与重量数据结合,得到体积重量和参考计费重量,并通过网页进行显示和运费估算。

二、项目效果
三、项目制作准备
所需硬件
- 行空板 K10(UNIHIKER K10)*1
- SEN0628: 矩阵激光测距传感器 *1
- HX711重量传感器套件 *1
- I2C 扩展模块
- PH2.0-4P数据线 *1(若干杜邦线)
- USB TYPE-C数据线 *1
接线关系
使用4pin线将I2C扩展模块接在行空板K10的I2C接口,使用杜邦线将8×8矩阵激光测距传感器和HX711重量传感器分别连接到I2C扩展模块上。

所需软件
我们需要先下载安装Arduino IDE,并完成行空板 K10 的 Arduino 开发环境配置,只有安装好行空板 K10 对应的 BSP,Arduino IDE 才能识别行空板 K10,并完成程序的编译和上传。具体安装过程可参考:https://www.unihiker.com.cn/wiki/k10/ArduinoIDE_prepare
导入库
- 同时我们还需要导入8*8矩阵激光测距传感器库文件(附件1),提供直接读取64点数据功能,导入方法为:Sketch(项目)→ Include Library(导入库)→ Add .ZIP Library(添加 .ZIP 库)。
- 同样还需要导入 重量传感器库文件(附件2)。该库用于完成行空板 K10 与重量传感器之间的通信,并提供初始化、去皮和重量读取等功能。

四、项目制作步骤

整个项目的实现是围绕“如何自动获得快递计费所需的完整数据”来设计整个系统。传统方式通常需要人工使用卷尺测量长、宽、高,再单独称重;本项目则利用 8×8 ToF 深度矩阵一次获得 64 个区域的距离信息,再结合传感器视场角和深度信息计算长、宽、高,相比单点激光测距,可以得到更完整的空间尺寸信息。重量传感器则负责获取包裹实际重量,两类传感器分别解决“包裹有多大”和“包裹有多重”的问题,并在行空板 K10 中进行数据融合,根据长、宽、高计算体积重量,再与实际重量比较得到计费重量。
随后行空板 K10 通过 USB 串口将尺寸和重量结果发送到网页端,网页利用 Web Serial API 读取数据,并提供寄收地址、快递公司等信息填写功能,再根据计费重量以及预设的首重、续重和地区价格规则计算预估运费。整个项目由“深度测量—重量采集—数据融合—网页交互—运费估算”几个部分组成,形成了从自动测量到实际业务应用的完整流程。

第一阶段:快递尺寸测量
我们先利用8×8 矩阵激光测距传感器的测距功能获取测量区域内 64 个位置的距离信息,再由行空板 K10 对这些深度数据进行处理,判断包裹所在区域,并计算出长、宽、高。为了让测量过程更加直观,行空板K10 屏幕还会同步显示 8×8 检测区域以及当前测得的尺寸。完成这一部分后,系统就可以在不使用直尺的情况下,自动得到包裹的三维尺寸。

整个尺寸测量实际上分成两个部分:高度通过空平台距离与箱顶距离的差值得到,而长和宽则通过包裹边界在 8×8 深度图中的位置,结合视场角和箱顶距离进行几何投影得到。
空场景学习
在进行放置快递包裹之前,系统需要先进行一次空场景学习,用来记录传感器到空平台的基准距离。程序启动后,8*8矩阵激光测距传感器会连续采集多帧深度数据,并分别记录 64 个测距区域的距离,通过中值处理得到较稳定的背景深度。完成学习后,这 64 个背景距离就会作为后续判断包裹是否出现以及计算包裹高度的参考数据。
// -------------------- 空场景学习 --------------------
void startBackgroundLearning() {
backgroundReady = false;
backgroundCount = 0;
// 清空历史背景数据
memset(backgroundDepth, 0, sizeof(backgroundDepth));
memset(backgroundSamples, 0, sizeof(backgroundSamples));
Serial.println("开始空场景学习,请保持平台为空");
}
// 连续采集多帧空平台深度数据
void addBackgroundFrame(const uint16_t *frame) {
if (backgroundCount >= BACKGROUND_FRAMES) return;
uint8_t validCount = 0;
for (uint8_t i = 0; i < ZONES; ++i) {
backgroundSamples[backgroundCount][i] = frame[i];
// 只统计有效距离点
if (validDistance(frame[i])) {
++validCount;
}
}
// 有效测距点足够时才保留这一帧
if (validCount >= BACKGROUND_VALID_ZONES_MIN) {
++backgroundCount;
}
// 收集足够帧后完成背景学习
if (backgroundCount >= BACKGROUND_FRAMES) {
finishBackgroundLearning();
}
}深度距离转换成长宽高
在完成空场景学习后,把快递放到平台上后,系统测得传感器到快递中心顶部的距离,例如为 350 mm。与之前空场景学习时传感器到空平台距离为500mm相比较,因此快递高度可以直接由两者之差得快递的高度150mm。
之后从 8×8 深度数据中找到包裹的左右、上下边界,再根据边界在传感器视场中的位置换算成对应角度。例如左右边界相对于中心分别约为 -15.9° 和 15.9°,而箱顶中心距离为 350 mm,利用三角投影关系可算得宽度大约200mm,同理当上下边界相对于中心分别约为 -23.2° 和 23.2°时,可算得长度大约300mm。
宽 = Ztop × (tanθR - tanθL)
= 350 × [tan(15.9°) - tan(-15.9°)]
≈ 200 mm
长度 = 350 × |tan(23.2°) - tan(-23.2°)|
≈ 350 × 0.8594
≈ 300.8 mm快速实现
硬件连接完成后,行空板K10通过USB线连接电脑,在 Arduino IDE 中选择行空板 K10 作为开发板,并选择对应的串口,然后打开完整的尺寸测量程序进行上传。


上传代码后,点击向右箭头的图标,开始运行并上传

尺寸测量实现完整代码
程序首先根据背景深度与当前深度的差值提取包裹区域,并保留最大的连续区域,减少噪声点干扰;随后分别统计横向和纵向的置信度分布,通过边缘插值得到包裹在 8×8 网格中的左右、上下边界。再结合传感器 的 60° 视场角,将网格边界转换为空间角度,并利用测得的包裹顶部距离,通过三角投影计算实际长、宽;包裹高度则由空平台背景距离与当前包裹顶部距离之差得到。最终将较大的水平尺寸作为长度、较小的作为宽度,并输出长、宽、高三项测量结果。
#include "Arduino.h"
#include "math.h"
#include "string.h"
#include "unihiker_k10.h"
#include "DFRobot_MatrixLidar.h"
UNIHIKER_K10 k10;
DFRobot_MatrixLidar_I2C tof(0x33);
// ---------- 参数 ----------
constexpr uint8_t GRID=8, ZONES=64;
constexpr float FOV_X_DEG=60.0f, FOV_Y_DEG=60.0f;
// ---------- 尺寸标定 ----------
// 标准箱实际尺寸:300 × 200 × 150 mm
// 当前稳定测量约:261 × 214 × 156 mm
// 只修正最终长/宽/高,不改变背景学习、前景提取和8×8色块显示。
constexpr float LENGTH_SCALE = 300.0f / 261.0f; // ≈1.1494
constexpr float WIDTH_SCALE = 200.0f / 214.0f; // ≈0.9346
constexpr float HEIGHT_SCALE = 150.0f / 156.0f; // ≈0.9615
constexpr uint16_t MIN_VALID_MM=20, MAX_VALID_MM=3900;
constexpr float FOREGROUND_START_MM=35.0f;
constexpr float FOREGROUND_FULL_MM=100.0f;
constexpr float MASK_LEVEL=0.35f;
constexpr float EDGE_MIN_LEVEL=0.50f, EDGE_FRACTION=0.60f;
constexpr uint8_t MIN_COMPONENT_ZONES=3;
constexpr float MIN_PACKAGE_HEIGHT_MM=45.0f;
constexpr uint8_t BACKGROUND_FRAMES=36;
constexpr uint8_t BACKGROUND_MIN_FRAMES=28;
con
// ...(中间代码较长,已截取核心头尾;完整代码请查看 DF 创客社区原文 https://mc.dfrobot.com.cn/article-386090.html )
ial.print("/");
Serial.print(displayedMeasurement.widthMm,1); Serial.print("/");
Serial.println(displayedMeasurement.heightMm,1);
}else{
Serial.println("--/--/--");
}
}
// ---------- 初始化 ----------
void setup(){
Serial.begin(115200);
delay(1000);
k10.begin();
k10.initScreen(SCREEN_DIR);
k10.creatCanvas();
k10.setScreenBackground(C_BG);
k10.rgb->brightness(20);
fillScreen(C_BG);
drawText("正在连接传感器",42,120,C_BLUE,Canvas::eCNAndENFont24);
k10.canvas->updateCanvas();
int r=tof.begin();
Serial.print("tof.begin result = "); Serial.println(r);
if(r!=0){
drawSensorError();
while(1) delay(100);
}
r=tof.setRangingMode(eMatrix_8X8);
Serial.print("setRangingMode result = "); Serial.println(r);
if(r!=0){
drawSensorError();
while(1) delay(100);
}
Serial.println("SEN0628 8x8 ready.");
delay(300);
startBackgroundLearning();
}
// ---------- 主循环 ----------
void loop(){
handleButtonA();
uint32_t now=millis();
if(now-lastFrameMs=DEPTH_FILTER_FRAMES) current=analyzeForeground();
updatePresenceState(current);
drawMainScreen();
printDiagnostics(current);
}
运行效果
由8*8矩阵激光传感器所测得数据,将深度距离转换为长宽高后为(29.6cm×19.4cm×14.9cm),快递的实际尺寸是(30cm×20cm×15cm),都保持在1cm以内的误差,具有真实可靠性。
第二阶段:加入重量检测
前面已经利用 8*8矩阵激光测距传感器完成了对快递长、宽、高的自动测量,但在实际寄件过程中,仅有尺寸数据还不足以确定最终的计费重量。因为快递计费通常还需要考虑包裹的实际重量,对于体积较大但重量较轻的包裹,还可能按照体积重量进行计算。因此,下一步在现有尺寸测量功能的基础上加入重量传感器,让行空板K10在测得长、宽、高的同时获取包裹实际重量,再将尺寸和重量数据结合起来,完成体积重量和计费重量的计算。
重量检测部分采用I²C 重量传感器。程序启动后首先完成传感器初始化和空载去皮,随后周期性读取重量数据。考虑到秤台受机械振动和传感器噪声影响,单次读数容易出现波动,因此程序先对传感器进行多次采样,再使用 5 点中值滤波去除瞬时异常值,同时设置零点死区,将小于 5 g 的微小波动直接归零,从而获得更加稳定的实际重量。
重量传感器代码:
// -------------------- 重量传感器初始化 --------------------
void initializeScale() {
// 初始化 HX711 I2C 重量传感器
scaleConnected = scaleI2C.begin();
if (!scaleConnected) {
Serial.println("HX711 not detected");
return;
}
// 设置校准系数并进行空载去皮
scaleI2C.setCalibration(HX711_CALIBRATION_VALUE);
scaleI2C.peel();
// 清空重量滤波状态
resetWeightFilterState();
Serial.println("HX711 I2C ready and tared.");
}
// -------------------- 重量读取 --------------------
void updateWeight() {
if (!scaleConnected) return;
// 读取4次重量并取传感器内部平均值
float grams = fabsf(scaleI2C.readWeight(4));
// 小于5g认为是零点抖动
if (grams < WEIGHT_ZERO_BAND_G) {
grams = 0.0f;
}
// 保存到历史窗口,后续进行中值滤波
weightHistoryG[weightHistoryHead] = grams;
weightHistoryHead =
(weightHistoryHead + 1) % WEIGHT_MEDIAN_WINDOW;
if (weightHistoryCount < WEIGHT_MEDIAN_WINDOW) {
++weightHistoryCount;
}
// 得到抗异常值后的重量
const float robustG = robustWeightMedian();
}在实际快递计费中,会存在体积重量和实际重量,其中体积重量由测得的快递“长、宽、高”单位先换算成厘米,之后算得快递体积(单位:CM3),再由快递的体积除以体积系数6000得到,然后再和快递的真实重量(单位:KG)进行比较,取较大的一个作为计费重量,然后在按照设定的计费规则向上取整。
计费重量代码(出处见正文参考链接):
// 将实际重量由克转换成千克。
const float actualWeightKg =
max(0.0f, filteredWeightG) /
1000.0f;
// 长度由mm转换成cm。
const float lengthCm =
displayedMeasurement.lengthMm /
10.0f;
// 宽度由mm转换成cm。
const float widthCm =
displayedMeasurement.widthMm /
10.0f;
// 高度由mm转换成cm。
const float heightCm =
displayedMeasurement.heightMm /
10.0f;
// 根据项目当前设置的体积系数6000,
// 计算体积重量。
displayedVolumeWeightKg =
(lengthCm *
widthCm *
heightCm) /
VOLUME_DIVISOR;
// 实际重量和体积重量中取较大者,
// 再按照程序设定的计费单位向上取整。
displayedChargeableKg =
roundChargeWeight(
max(
actualWeightKg,
displayedVolumeWeightKg
)
);运行效果
加入重量传感器之后,测得快递的真实重量为0.87kg,而所测得快递尺寸数据为(29.5cm×20.0cm×15.1cm)(每次测量时可能会因为放的位置不一样,所以测量结果会存在比较微小的波动,但并不影响最后预估收费),所以体积重量为(29.5×20.0×15.1÷6000)1.49kg。
第三阶段:通过网页获取寄件价格
我们已经完成了包裹长、宽、高以及实际重量的自动采集,行空板 K10 也能够在屏幕上直接显示测量结果。在实际寄快递时,后面还需要填写寄件人和收件人信息、选择快递公司,并根据包裹尺寸和重量进一步估算运费。因此,在完成尺寸与重量检测之后,我又采用 HTML、CSS 和 JavaScript 加入了实时网页端,将行空板 K10 采集到的数据通过 USB 串口继续发送到电脑,网页使用浏览器 Web Serial API 直接读取并解析串口数据,再将测量结果实时显示在页面中。用户随后填写寄收地址并选择快递公司,网页根据计费重量匹配预设的首重、续重和地区价格规则,计算预估运费。
我们可以下载html文件(附件3),双击打开之后,点击串口连接(此时Arduino IDE里的串口一定要关闭),然后行空板K10的实时数据数据就会显示在这个界面上,然后我们可以填写收寄地址及用户信息,并选择快递承运商和以及服务模式。
第四阶段:获取预估价格
根据常见快递公司平台公布的常规寄件报价,做一个相对应的价格表,对应不同的收费标准,采用的是首重+续重的标准,首重1KG,当超出首重时,按续重收费,续重费用随着不同承运商和收寄地址远近而不同,续重不足1KG的按1KG算。当用户选择要寄件的快递公司时,系统会自动计算相应的预估报价。
当前项目主要用于原型验证,因此采用本地价格表模拟报价,后续可进一步接入快递公司的官方接口或第三方物流 API,实现真实运费查询和下单。使整个项目从“自动测量”进一步扩展为“测量 + 信息填写 + 运费估算”的完整流程。
完整代码
你也可以根据下方完整代码(或者下载附件4),实现整个功能!
#include "Arduino.h"
#include "Wire.h"
#include "math.h"
#include "string.h"
#include "unihiker_k10.h"
#include "DFRobot_MatrixLidar.h"
#include "DFRobot_HX711_I2C.h"
UNIHIKER_K10 k10;
// -------------------- 硬件参数 --------------------
static constexpr uint8_t HX711_I2C_ADDRESS = 0x64;
// I2C 模块的校准值通常由模块自动校准得到;这里使用库的默认值。
static constexpr float HX711_CALIBRATION_VALUE = 2236.0f;
static constexpr float WEIGHT_ZERO_BAND_G = 5.0f;
// -------------------- 重量换算规则 --------------------
// 体积重单位:kg = 长(cm) × 宽(cm) × 高(cm) / 6000。
// 参考计费重:实际重量与体积重量取较大值,再按 0.5 kg 向上取整。
// 注意:不计算运费。真实运费还需要寄件地、收件地、快递公司等信息。
static constexpr float VOLUME_DIVISOR = 6000.0f;
static constexpr float CHARGE_UNIT_KG = 0.5f;
static constexpr float WEIGHT_STABLE_DELTA_G = 3.0f;
static constexpr uint8_t WEIGHT_STABLE_FRAMES = 4;
static constexpr uint32_t WEIGHT_READ_INTERVAL_MS = 100;
static constexpr uint8_t WEIGHT_MEDIAN_WINDOW = 5;
static constexpr float WEIGHT_FAST_JUMP_G = 35.0f;
static constexpr float WEIGHT_MEDIUM_
// ...(中间代码较长,已截取核心头尾;完整代码请查看 DF 创客社区原文 https://mc.dfrobot.com.cn/article-386090.html )
tem");
k10.begin();
k10.initScreen(SCREEN_DIR);
k10.creatCanvas();
k10.setScreenBackground(C_BG);
k10.rgb->brightness(20);
setRgb(C_YELLOW);
// SEN0628:严格使用之前在 Arduino IDE 中已经验证成功的语法
int beginResult = tof.begin();
Serial.print("tof.begin result = ");
Serial.println(beginResult);
if (beginResult != 0) {
Serial.println("SEN0628 BEGIN FAILED");
drawSensorError();
while (1) delay(100);
}
Serial.println("SEN0628 BEGIN SUCCESS");
int modeResult = tof.setRangingMode(eMatrix_8X8);
Serial.print("setRangingMode result = ");
Serial.println(modeResult);
if (modeResult != 0) {
Serial.println("8x8 MODE FAILED");
drawSensorError();
while (1) delay(100);
}
Serial.println("8x8 MODE SUCCESS");
initializeScale();
startBackgroundLearning();
}
void loop() {
handleButtons();
updateWeight();
const uint32_t now = millis();
if (now - lastFrameMs < FRAME_INTERVAL_MS) {
delay(2);
return;
}
lastFrameMs = now;
uint8_t readResult = tof.getAllData(rawDepth);
if (readResult != 0) {
Serial.print("getAllData failed: ");
Serial.println(readResult);
delay(20);
return;
}
if (!backgroundReady) {
addBackgroundFrame(rawDepth);
drawLearningScreen();
return;
}
updateDepthMedian(rawDepth);
Measurement current;
if (historyCount >= 3) current = analyzeForeground();
updatePresenceState(current);
updateBilling();
drawMainScreen(current);
printDiagnostics(current);
}
五、总结
我基于行空板 K10、8×8 矩阵激光测距传感器和重量传感器,实现了一套集 包裹尺寸测量、重量检测、计费重量计算以及网页运费估价 于一体的智能快递测量系统。设备启动后会先进行空场景学习,建立平台的 8×8 背景深度数据;当包裹放入测量区域后,通过背景差分识别包裹区域,再结合边界位置、传感器视场角和深度数据计算包裹的长、宽、高,同时由重量传感器获取实际重量。
这个项目也让我进一步理解了 8×8 ToF 阵列并不只是简单读取 64 个距离值,更重要的是如何通过 空场学习、深度滤波、背景差分、边界提取和三角投影,把这些离散的距离数据转换成具有实际意义的尺寸信息。后续还可以加入摄像头视觉识别、更高分辨率深度传感器或真实物流报价接口,让整套系统进一步向实际的智能寄件终端靠近。











求后续更新,期待更多细节