news 2026/10/22 2:51:15

基于Labelme的树干分割数据集制作与模型训练实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Labelme的树干分割数据集制作与模型训练实战指南

手上如果有一批林地拍摄的图像,想要单独把树干区域切出来,你会怎么做?也许第一反应是用现成的检测模型框出树,但框只给矩形,树干的弯曲、分叉、被遮挡部分根本表达不清楚。真正要用于测量胸径、计算材积、规划修剪路径,需要的是像素级分割结果。所以我一直在折腾“树木树干识别分割数据集”,用常见的开源标注工具Labelme做了1315张,只标一个类别:树干。单类别听着简单,真正做起来涉及数据采集、标注规则、格式转换、模型训练一长串细节。这篇文章把我的实操过程、踩坑记录、转换脚本、训练参数选择全部整理出来,给同样在做林业视觉或者想入门语义分割的朋友一份可参考的模板。

1. 这个数据集到底解决什么问题

1.1 为什么只做一个“树干”类别

很多人看到“1类别”第一反应是:是不是标得不够仔细?其实不是。在树的分割任务里,常见做法会把树冠、树干、背景分开,甚至把不同树种单独标。但实际项目里,如果需求只是“测量树干位置和粗细”,那多类别反而有害。因为模型会花一部分容量去区分树冠和树干边界,而树冠的形态复杂、颜色与背景接近,容易把分割结果带偏。我做这个数据集时,明确需求是:只要树干。树冠、树枝末梢、地面、天空、小草,全部并成背景。这样训练出来的模型目标非常集中,树干边界学得更干净。

那“树干”的物理边界到底画到哪里?我定的规则是:从树干与地面或积雪的交界处开始,到第一次明显分叉或树冠底部结束。如果树是倾斜的,就顺着主干的可见轮廓走,不要把斜出的侧枝算进去。这个规则在标注前一晚就定死,并且要求所有标注人员统一执行,因为分割标注最怕标准漂移。今天这个人把枝杈算进去了,明天那个人把阴影算进去了,模型学出来的东西就四不像。

1.2 1315张的数据规模合理吗

可能有人觉得1315张太少。但分割任务和分类不同,一张图里往往有多个样本区域,而且像素标注带来的监督信号非常密集。只要图像内容多样性足够,1300多张完全能启动一个baseline。我自己实测下来,一个U型分割网络在这个数据上训练几十轮,验证集IoU能到0.8以上。关键是图像不能是“看上去都差不多的”同一个场景连拍,而要有足够多的拍摄时间、距离、角度变化。

1315这个数量对标注工作强度来说也是比较合适的。如果一个人全职标注,采用精心描点的方式,一天能完成80到100张,1315张大概需要两周。配合另一名审核人员进行交叉检查,一个月内可以把质量做得很扎实。再多容易疲劳导致标注质量下滑,再少则训练时容易欠拟合。所以1315并不是一个随手拍的数量,而是综合考虑标注成本、样本多样性和模型训练需求后的选择。

2. 数据采集和初始整理

2.1 拍摄条件与采集路线

数据来源是某林区的样地拍摄,也有部分来自园区和道路绿化。为了不让模型只适应单一背景,我设计了几条采集路线:林间密植区、空旷草地单棵树、水泥路边的行道树、有灌丛遮挡的混交林。每个点位的拍摄距离控制在1米到5米之间,因为树干分割的应用场景一般是近景测量,太远的照片树干细节少,标出来也没有意义。

拍摄时间上刻意覆盖了早晨逆光、正午强光、傍晚暗光三种情况。还加了雨天之后的湿树干和部分雪天场景。实际经验是:逆光和暗光是模型最容易翻车的地方,早晚期数据比例我会稍微加大。拍摄设备没有用特殊仪器,手机和普通微单都可以,但照片像素至少要求两千万以上,因为后面标注时需要放大看纹理边缘,不然靠猜的部位太多。

采集回来的原始图片不能直接拿来标注。首先要把尺寸统一,我用脚本把所有长边缩放到1920像素,短边按比例缩放,不足的用黑色填充,不做拉伸。统一尺寸是为了Labelme打开的时候不会卡顿,同时训练时不需要频繁处理多分辨率。还要剔除模糊、严重过曝、失焦的图片,我当时的筛选比例大约是原始图的15%。如果原图里大部分树干都被树叶完全盖住,也要删掉,这种图像标注不出来有效区域。

2.2 图像筛选与统一预处理

这里有一个容易被忽略的点:删除重复度太高的图。如果在同一个位置连续拍摄了一两百张,构图几乎一样,模型会把这些重复图当成不同样本,训练时产生严重过拟合,验证集还显得特别高。我用了简单的方法:先计算每张图的感知哈希,再按相似度阈值做去重,至少保证相邻拍摄点之间画面有30%以上的变化。

预处理阶段还有一个“信息脱敏”工作,因为实际采集可能拍到人脸、车牌等无关信息。我不要这些内容,也不希望数据集里留下可识别的个人信息,于是用检测脚本把画面边缘出现的人脸区域直接裁掉,或者如果人脸占比太大就整张丢弃。这个问题很多人不重视,但数据合规是所有数据集必须过的第一道关卡。

除去重和脱敏,我还会批量检查EXIF信息,把异常色偏的图用白平衡脚本纠正一下。注意不要做过度的图像增强,比如锐化、饱和度拉满,这些会改变真实纹理信息,导致标注时看不清树皮裂缝,训练出来也会引入偏差。

3. Labelme标注实操

3.1 标注环境搭建与基本操作

Labelme是一个基于Python的开源标注工具,安装非常省事。我用的是命令行版本,环境是Python 3.8以上,直接执行安装命令就行。安装完成后,不要急着双击启动,先把当前终端切到存放图片的目录,再输入启动命令,这样打开图片时相对路径保存得干净,后面转换脚本省掉不少拼接路径的麻烦。

打开标注界面的操作很直观:左侧工具栏选择创建多边形,然后沿着树干边缘打点。Labelme没有自动吸附边缘的功能,所以标注是个纯手工活。为了降低劳动强度,我会先用边缘检测算法生成一张伪彩色边缘图,叠加在原图上作为参考层。这个参考层不是直接自动生成标注,而是帮助人眼判断,效率提升大约20%,而且边缘断断续续时能提醒标注者补全。

一个我建议所有新手上手就做的事情是:先把Labelme的快捷键记熟。多边形完成后保存为JSON,双击可以改变点的位置;按住Ctrl可以删除一个点;右键拖拽可以平移画布。我一开始没用快捷键,一个多边形要花5分钟,熟练后平均每张图控制在4分钟以内。

3.2 标注规范详解:边界、遮挡和点密度

标注树干这种带长条形状的目标,最难的是边界判断。尤其是树干与地面交界处,草丛或泥土会把树皮纹理掩盖一部分,这时候我定的规范是:以可见树皮的最外侧边缘作为边界,被草丛遮挡的部分直接框到草丛边界上,不向下猜测延伸。这样做的好处是模型不会学到“树干下面有一块绿色”,而是学会在树干可见部分稳定输出。

分叉处理同样要严格。一个主树干长出两三个侧枝时,我选择把侧枝基座的过渡部分包含在标注里,但不超过10像素。如果侧枝长得像另一根独立树干,比如丛生灌木的分枝,那就只标注其中最粗壮的一根。反正项目的最终目标是测胸径,标注出主干位置才是关键。

点密度是很多人忽视的地方。标注一个多边形,点打得太疏会导致边界锯齿严重,训练出来的mask边缘粗糙;点打得太密会导致保存的JSON文件体积大,转换后的polygon点数太多,训练时计算效率低。我实际操作下来的标准是:圆滑弧线段每20到30像素一个点,直线段可以放宽到80像素以上;树干边缘本身的自然弯曲处多打点,直线段少打点。整体一个树干的多边形控制在20到40个点就够了,不需要数百个点。

3.3 Labelme 格式文件结构分析

Labelme保存的JSON文件结构并不复杂,我把它拆开看过一遍之后,后面写转换脚本心里就踏实了。几个关键字段说一下:

  • version:标注器版本号,不重要,但转换时最好保留。
  • flags:一个空字典或自定义标签开关,我用不到,默认空。
  • shapes:核心部分,它是列表,每个元素对应一个标注对象。每个对象里label是一个字符串,比如“trunk”;points是多边形顶点的二维坐标列表;shape_type可以理解为多边形的类型,我用的是“polygon”;还有group_id用于区分同一实例的不同部分,我用null。
  • imagePath:原始图片路径,因为标注时就在原图目录下,所以会是相对路径。
  • imageData:这个字段非常关键,Labelme默认会把图片的base64编码数据写进去。如果图片多,这个字段会让JSON文件体积爆炸。保存的时候我可以指定不嵌入图片数据,只保留路径,这样转换时再读原图。

下面是一个简化的JSON片段,让你感受一下结构:

{ "version": "5.0.1", "flags": {}, "shapes": [ { "label": "trunk", "points": [[120, 40], [135, 45], [150, 68], [160, 100], [155, 150]], "group_id": null, "shape_type": "polygon", "flags": {} } ], "imagePath": "sample_001.jpg", "imageData": null }

保存的时候注意,如果imageData是null,后面读取标签时不能直接依赖json里的图片,需要按imagePath去原图目录找。这个细节让我在批量转换时避免了很多空指针问题。

4. 从标注文件到训练集

4.1 为什么不能直接用 Labelme json 训练

主流分割模型训练时,需要的目标格式一般有两种:一种是每个像素都有类别标签的mask图像,另一种是COCO或YOLO的分割标注格式。Labelme的json只是中间态,不能直接丢给训练脚本。原因很简单:模型训练要频繁读取像素级标注,每次解析json、用多边形填充mask的效率太低。尤其是COCO格式还要求把polygon转换成矩形bbox和细分后的segmentation列表,这个过程如果不在预处理阶段完成,训练时会变成性能灾难。

另外,Labelme里的points是绝对像素坐标,而很多模型训练框架要求输入尺寸固定,比如512x512或者640x640。转换时需要把坐标按比例缩放,同时mask也要做同样的插值。这些操作放在训练预处理管线里当然也可以,但会大量重复计算。我习惯在格式转换阶段就把坐标和mask一次性处理成标准尺寸,训练时只读数组,速度能快不少。

4.2 格式转换脚本的思路与实现

我的转换脚本分三步走:第一步读取所有json文件,按文件名排序并跳过损坏文件;第二步创建一个全黑的mask图,用cv2.fillPoly把每个多边形的内部填成白色,也就是类别索引1;第三步把坐标归一化后存成训练所需的数组或COCO注解。这个流程写下来不复杂,但有几个坑需要注意。

一个坑是图片读取要用实际图像尺寸,不能直接用json里可能写错的宽高。另一个坑是fillPoly的坐标必须是整数,如果坐标是浮点数,会在边界产生瑕疵。我在脚本里先对所有点做四舍五入,再统一填充。还有一个经验是:如果一张图里有多棵树,多个多边形之间不能重叠。标注时如果两个多边形相互交叉,转换后mask会出现覆盖顺序的差别,属于脏数据。我在转换脚本里加了一步重叠检测,发现有交叉区域时输出警告,然后人工回去修标注。

下面是生成单类别mask的Python脚本骨架:

import json import numpy as np import cv2 import os def labelme_to_mask(json_path, output_dir): with open(json_path, 'r', encoding='utf-8') as f: ann = json.load(f) img_path = os.path.join(os.path.dirname(json_path), ann['imagePath']) img = cv2.imread(img_path) h, w = img.shape[:2] mask = np.zeros((h, w), dtype=np.uint8) for shape in ann['shapes']: points = np.array(shape['points'], dtype=np.int32) cv2.fillPoly(mask, [points], 1) out_path = os.path.join(output_dir, os.path.basename(img_path)[:-4] + '.png') cv2.imwrite(out_path, mask)

你也可以把遮罩编码成RLE或者转换为COCO的annotation dict,但最初阶段一个简单的png mask足够用了。

4.3 数据集划分与增强策略

数据集初始划分我采用82,剩下的18作为验证集,测试集则从训练集中单独抽出一部分固定保留。我的习惯是先划分,再做增强,绝不能在增强后才划分,否则会引入数据泄露。最稳妥的方式是用文件名列表随机打乱,固定随机种子,保证多次实验用同一批验证集。

针对1315张的量级,数据增强是必须的。我用的增强组合包括:水平翻转、小幅旋转(正负10度)、亮度对比度扰动、随机裁剪。要注意的是,分割任务里几何变换必须同步作用于原图和mask,因此最好用现成的增强库,在同一个管道里完成。不要单独翻转mask然后忘记翻转原图,这种问题我犯过不止一次。增强倍数放在3倍左右,也就是最终训练图大约4000张,这个量级既不会让训练太慢,也能有效压制过拟合。

一个有趣的点是垂直翻转对树干任务没什么帮助。因为树干的纹理有方向性,树皮裂纹通常沿主干方向延伸,垂直翻转会把这种方向信息打乱,导致训练时模型额外学习一个不存在的分布。所以我的增强策略里特意不放垂直翻转。

5. 模型训练与效果评估

5.1 分割模型选型对比

针对单类别树干分割,常见的模型有几种路线。如果追求快速迭代,我建议从U型分割网络开始。它结构简单,在小数据集上表现稳定,而且显存占用不大。第二个选择是带空洞卷积的DeepLabV3+,它对长条形的目标边缘更友好,因为感受野更大,能捕捉到树干整体走向。第三种是实例分割网络,像Mask R-CNN,如果一张图里有多棵树且想区分每一棵,就选它。第四种是近期比较流行的YOLO分割系列,输出速度快,适合部署到嵌入式设备。

我用过U型网络和DeepLabV3+做对比,U型网络在边缘粗糙度上略高,但IoU差距不大。考虑到训练时间和部署难度,我最终选的是U型网络加一个轻量级的编码器。做分割模型选型其实不需要追新,关键是匹配你的数据体量和计算资源。1315张的数据量喂给超大规模模型,很容易直接把训练集背下来,验证集反而不动。

5.2 小数据量训练技巧

小数据量训练的第一技巧是迁移学习。先用一个在图像分类数据集上预训练过的编码器初始化主干部分,哪怕预训练任务和树干分割差得很远,也能帮模型学会低层纹理特征。第二个技巧是减少学习率,我在微调阶段初始学习率设在1e-4,比从头训练低一个量级,避免破坏预训练权重。损失函数我用了带边界惩罚的组合损失,单纯交叉熵会把边界像素的错误放大,再加上Dice损失后,整体IoU提升1到3个百分点。

训练过程中务必打开早停回调,监控验证集IoU,连续十几个epoch没提升就停止保存最佳模型。我一开始不设早停,训练50轮后看loss还在降,但IoU早就平台了,白白浪费算力。1315张图配三倍增强,在单卡配置下,一个U型网络训练一轮大约需要2到3分钟,总共20到30轮就能达到稳定结果。

5.3 评价指标与实测结果

分割任务最常用的评价指标是IoU和Dice。IoU是预测mask和真实mask交集面积除以并集面积,Dice比IoU在数值上更乐观一些,但两者趋势一致。我统计的指标是在固定验证集上逐张计算,然后取所有类的平均。因为我的数据只有一个前景类,背景类虽然占比大,但不能只算背景,否则模型什么都不预测也能获得高分。

我跑完一轮基线,验证集的IoU大约能到0.83到0.86之间。这个成绩在林地复杂背景下已经算可用。如果要把结果落地到移动端,我还会用一个NMS后处理方法去掉小于某个面积阈值的连通域,因为树干必须是连片的。这个方法在推理阶段只消耗不到10毫秒,却能明显减少小块误判。

6. 常见问题与排查实录

6.1 标注质量问题

做数据集期间我遇到最频繁的问题是边界漂移。同一个标注员前后两张图,前一棵树的边界贴着树皮外缘,后一棵树却画到了阴影里。这个问题看不出脏乱,但会极大影响分割精度。解决方法是每周做一次抽样审阅,把不同标注员的同一张图让另外一个人重新标,对比重合度。如果不一致的地方超过20%,就需要重新培训规范。

还有一个问题是树干和背景颜色相近时的标注犹豫。尤其是灰白色树皮和灰色天空,或者棕色树干和泥土,人眼都容易混淆。我的建议是不要依赖颜色,而是依赖纹理和遮挡关系。树皮边缘通常有一条不规则的明暗过渡,把黑暗过渡的内侧当作树干边界。如果你实在不确定,宁可稍微往里收一点,也不要往外扩。因为内部偏差在之后的形态学腐蚀处理中还能修补,外扩会把背景学进来。

6.2 转换与训练报错排查

转换脚本最常见的报错是中文路径问题。Windows系统上Labelme保存的json路径如果包含中文,cv2.imread可能读不到图片,返回None。解决方法是把所有路径统一改成英文,或者在脚本开头强制设置编码格式。我踩过这个坑后,直接把整个数据集目录改名成纯英文的trunk_seg,后续所有环节都轻松了。

训练时的显存不足也很常见。如果输入原图分辨率过高,比如1920宽直接输入模型,即使batch很小也会把显存吃满。我的做法是把输入尺寸缩到512x512或640x640,并且用随机裁剪替代全图输入,这样显存占用直线下降。另一个容易忽略的问题是mask里类别索引必须从0连续到n-1,如果只有树干是1,背景是0,没问题;但如果漏了背景类,有些框架会报错。

6.3 Labelme使用心得

最后说说Labelme工具本身。它是一个很成熟的工具,但也有一些使用细节:保存JSON时尽量关闭图片嵌入,否则一个文件夹几百MB,备份和传输都痛苦;打开大图卡顿时,先缩小图像显示再标注;如果标注到一半软件崩溃,JSON没保存,Polygon点会全部丢失,所以我会每标注完一棵树就按一次保存,而不是全部标完再保存。

另一个小技巧是,当图像里有多棵树且距离很近时,不要在一个多边形里框两棵树。多棵树分开标,中间哪怕只有一个像素的空白,也表示它们不是同一个树干实例。如果你希望后续做实例分割,这个区分特别重要。我习惯在标注时给每个多边形一个可视化颜色,Labelme允许自定义分类配色,树干统一用纯红色,这样查看时特别明显,也方便质检。

7. 后续扩展方向

这个数据集目前是单类别,但它完全可以扩展出更多用途。一个自然的方向是增加“树枝”或“树冠”类别。有了这三类分割结果,整棵树的骨架结构可以被重建出来,用于计算冠层覆盖率或者修剪方案规划。另一个方向是把树干分割结果和深度信息结合,通过单目深度估计获取大致的树干半径,进而估算木材体积。

如果把标注点数进一步细化,还能做树干关键点检测。比如语义分割之外,额外标注胸径位置的上下两个关键点,通过两点间的像素距离换算单位长度。这个做法在自动林木测量机器人里非常实用。我在实际项目中已经开始给一些样本添加这类关键点,相当于同一个标注文件里既有polygon又有point,训练时两个任务共享一个编码器,效果比单独跑两个模型更稳定。

如果你打算把这个数据集开源或者共享给团队,建议固定一个命名规范:图片文件名包含拍摄点和序号,标注文件与之同名,转换后的mask文件名也保持一致。同时准备一个包含图像数量、类别定义、标注规范的说明文档,这样别人接手时不会重新摸索标注边界规则。数据集的价值不在文件数量,而在标注的一致性和可验证性。

我个人做下来最深的体会是:一个看似简单的1类别分割数据集,真正的工作量集中在规则细化和质量检查上,而不是模型训练。先把标注定义清楚,让所有人都能复现,后面训练和部署都会很顺。如果只急着把图扔进标注工具,最后整理数据时一定会返工。希望这个流程对你有帮助。

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

RFID与视觉融合的仓库巡检智能小车:从硬件选型到工程落地

年初我在某中转仓做库存巡查方案时,发现最耗人手的不是装卸,而是盘库和外观巡检。仓库主管跟我说:地面货摆得又多又密,让两个人拿着扫码枪一排一排扫,扫完数据还经常对不上账。我当时就提了一个想法:与其让…

作者头像 李华
网站建设 2026/10/22 2:50:46

DCCA原理与MATLAB实现:多视图非线性特征对齐技术详解

简介:本资源为深度典型相关分析(DCCA)的完整开源实现合集,面向机器学习、多模态学习及跨视图表示学习方向的研究者与工程实践者,旨在解决传统CCA在非线性、高维多源数据关联建模中的局限性。压缩包含86个文件&#xff…

作者头像 李华
网站建设 2026/10/22 2:50:30

基于Spring Boot的智慧工厂安全生产监督管理系统开发实战

做毕设选“基于Spring Boot的智慧工厂安全生产监督管理系统”这个题目的同学,最近几年越来越多。每次一聊到这个题目,我的第一反应就是:这是一个典型的“看着容易、做起来杂”的管理系统。它不像算法题那样需要烧脑,但涉及的模块多…

作者头像 李华
网站建设 2026/10/22 2:50:30

AI辅助毕业论文写作:选题框架、文献综述与查重降重实战

1. 论文季的痛,我懂你也懂每年三四月份,朋友圈里充斥着“毕业难”的哀嚎。我当年也是这么过来的,白天在实习单位摸爬滚打,晚上回到宿舍盯着Word文档发呆,那种“憋不出一个字又想赶紧写完”的焦虑感,至今记忆…

作者头像 李华
网站建设 2026/10/22 2:50:24

2026算法备案实操指南:从产品盘点到年检全流程

2026年的算法备案申报周期,比往年更早被大家提上日程。最近身边做AI产品的朋友扎堆来问,第一个问题往往是:我们就是接了一个大模型API,这也要备案吗?还有人卡在填报系统里,材料改了三轮还是被退回&#xff…

作者头像 李华
网站建设 2026/10/22 2:49:55

基于SSM的游戏账号租赁小程序:从数据库设计到订单状态机实战解析

说实话,第一次看到“基于SSM的游戏账号租赁小程序”这个题目时,我第一反应是:这到底是正经的电商平台,还是毕业设计凑出来的伪需求?但等我真正动手做了demo、跑通整套订单流之后,我得承认自己被打脸了。游戏…

作者头像 李华