
简介这份数据集面向计算机视觉入门者、深度学习教学者以及需要轻量级分类实验的开发者提供星形、圆形、正方形、三角形四类基本几何形状的标注图像可用于图像分类模型的训练、验证与教学演示帮助快速搭建形状识别实验环境。资源包共收录14973个文件其中14970个为png格式图片另有3个py脚本压缩包整体约21.57MB图片统一为200×200像素尺寸规整、类别清晰便于直接读取与批量预处理。目前已有293人学习下载适合作为课程作业、模型对比或数据增强练习的素材。读者可获得一套开箱即用的四分类图像数据配合附带的Python脚本可快速完成数据加载与目录遍历省去自行采集与清洗的成本也能在此基础上尝试迁移学习、卷积网络调参等进阶任务是入门图像分类的实用起点。1. 四种基本形状图片数据集从零训练一个形状分类器到底值不值手头有个小需求给流水线上的零件做初筛只判断它是星形、圆形、正方形还是三角形。你不需要 ImageNet 级别的模型也不需要 GPU 集群一张 224×224 的小图、一个四分类头几秒钟就能出结果。问题在于——数据从哪来网上现成的形状数据集要么是手写数字那种灰度图要么是几何图形合成图类别不齐、分辨率参差拿来做真实场景的迁移几乎必翻车。四种基本形状的图片数据集星形、圆形、正方形、三角形就是为这类场景准备的类别少、语义清晰、可控性强适合做教学演示、原型验证、边缘设备上的轻量分类。这篇文章不讲空泛的“数据集很重要”而是把从拿到数据到跑通推理的整条链路拆开告诉你每一步的参数怎么设、坑在哪、什么情况下这个方向不值得投入。如果你正在做形状识别、几何特征分类、或者想找一个干净的小数据集练手下面的内容可以直接抄作业。2. 形状数据集怎么选、怎么造合成与实拍的取舍2.1 四种形状的数据集长什么样才算“能用”一个能直接训练的形状数据集核心不是图片数量而是类间差异是否被噪声淹没。星形、圆形、正方形、三角形这四类在理想几何条件下是线性可分的但一旦加入旋转、缩放、光照、背景纹理边界就模糊了。我一般会先检查三个指标每类样本数是否均衡偏差不超过 1.2:1、图像分辨率是否统一至少短边 128px、背景是否单一。如果背景里混入了文字、线条、其他形状的残影模型会学到“背景纹理”而不是“形状轮廓”这是最常见的翻车点。合成数据集的优势在于标注零成本、类别绝对均衡。用 OpenCV 或 PIL 画一个星形顶点坐标固定旋转角度可控背景纯白或纯黑生成一万张也就几十秒。但合成数据的致命伤是域偏移真实摄像头拍出来的形状有透视变形、边缘模糊、反光合成图里没有。我的做法是合成打底、实拍微调——先用 8000 张合成图训练一个基础模型再用 200 张实拍图做最后一层微调准确率能从 0.82 拉到 0.94 左右。实拍数据集则相反标注成本高但泛化好。如果你只有手机能拍建议在纯色卡纸上剪出四种形状固定光源、固定距离每类拍 300 张旋转角度覆盖 0° 到 360°。注意不要只拍正面倾斜 15° 到 30° 的样本必须占至少 20%否则模型对透视变换毫无抵抗力。2.2 用 Python 合成四种形状图片的最小脚本下面这段代码用 PIL 生成星形、圆形、正方形、三角形各 500 张随机旋转、随机缩放、随机背景色输出到四个子目录。依赖只有 Pillow 和 numpy。import os import math import random from PIL import Image, ImageDraw # 输出根目录每个类别一个子文件夹 ROOT shapes_dataset CLASSES [star, circle, square, triangle] IMG_SIZE 224 SAMPLES_PER_CLASS 500 # 随机背景色范围浅灰到白避免纯黑导致边缘对比过强 BG_RANGE (200, 255) # 形状填充色范围深色与背景形成对比 FG_RANGE (0, 80) def random_color(low, high): return tuple(random.randint(low, high) for _ in range(3)) def draw_star(draw, cx, cy, r, fill): # 五角星外顶点半径 r内顶点半径 r*0.4 points [] for i in range(10): angle math.pi / 2 i * math.pi / 5 radius r if i % 2 0 else r * 0.4 x cx radius * math.cos(angle) y cy - radius * math.sin(angle) points.append((x, y)) draw.polygon(points, fillfill) def draw_triangle(draw, cx, cy, r, fill): # 等边三角形顶点朝上 points [] for i in range(3): angle math.pi / 2 i * 2 * math.pi / 3 x cx r * math.cos(angle) y cy - r * math.sin(angle) points.append((x, y)) draw.polygon(points, fillfill) def generate_one(cls, idx): img Image.new(RGB, (IMG_SIZE, IMG_SIZE), random_color(*BG_RANGE)) draw ImageDraw.Draw(img) # 中心点随机偏移 ±20px防止模型只学“居中”特征 cx IMG_SIZE // 2 random.randint(-20, 20) cy IMG_SIZE // 2 random.randint(-20, 20) # 半径随机 40 到 80控制形状大小 r random.randint(40, 80) fill random_color(*FG_RANGE) if cls star: draw_star(draw, cx, cy, r, fill) elif cls circle: draw.ellipse([cx - r, cy - r, cx r, cy r], fillfill) elif cls square: draw.rectangle([cx - r, cy - r, cx r, cy r], fillfill) elif cls triangle: draw_triangle(draw, cx, cy, r, fill) # 随机旋转 -30 到 30 度模拟拍摄角度偏差 angle random.uniform(-30, 30) img img.rotate(angle, resampleImage.BILINEAR, expandFalse) return img for cls in CLASSES: out_dir os.path.join(ROOT, cls) os.makedirs(out_dir, exist_okTrue) for i in range(SAMPLES_PER_CLASS): img generate_one(cls, i) img.save(os.path.join(out_dir, f{cls}_{i:04d}.png)) print(done)逻辑说明draw_star用极坐标生成十个顶点外顶点半径r、内顶点r*0.4这是五角星的标准画法。draw_triangle同理三个顶点均匀分布在圆周上。中心点偏移和随机旋转是为了让模型不依赖绝对位置和角度。BG_RANGE和FG_RANGE的取值决定了对比度如果背景太暗、形状太亮边缘检测会失效所以我把背景限制在浅色区间。参数说明IMG_SIZE改成 128 可以加快训练但会损失细节SAMPLES_PER_CLASS建议不低于 300否则四分类的验证集波动会很大r的范围 40 到 80 对应形状占图比例约 18% 到 36%太小则形状像素不足太大则旋转后容易出界。生成完检查一下每个文件夹的图片数量是否一致不一致说明某类生成时抛了异常被吞掉。2.3 实拍补充手机拍 200 张的采集规范合成数据跑通后如果目标场景是真实摄像头必须补实拍。我一般用手机固定在三脚架上俯拍一张 A4 纸纸上贴四种形状的卡纸。每换一个形状旋转 8 个角度0°、45°、90°……315°每个角度拍 6 张加上不同光照顺光、侧光、背光各 2 张总共 4×8×6192 张接近 200 张。拍完用 LabelImg 或 Label Studio 标注但四分类任务其实不需要画框直接按文件夹分好即可。注意实拍图要统一裁剪到 224×224裁剪时保证形状居中边缘留 20px 余量。如果实拍图里出现了合成数据没有的阴影不要试图用后期去掉保留阴影反而能提升模型鲁棒性。3. 用 PyTorch 训练四分类模型从 DataLoader 到混淆矩阵3.1 数据划分与增强策略的参数怎么定拿到shapes_dataset后按 7:2:1 划分训练集、验证集、测试集。注意如果合成图和实拍图混在一起必须保证同一张原图的不同增强版本不会跨集出现否则验证集准确率会虚高。我一般先用torchvision.datasets.ImageFolder加载再用random_split按类别分层划分。增强策略只保留三种随机水平翻转、随机旋转 ±15°、颜色抖动亮度 0.2、对比度 0.2、饱和度 0.2。不要加随机裁剪因为形状本身占图比例不大裁多了会把形状裁掉。import torch from torch.utils.data import DataLoader, random_split from torchvision import datasets, transforms # 训练增强翻转、旋转、颜色抖动 train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(degrees15), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), transforms.ToTensor(), transforms.Normalize(mean[0.5, 0.5, 0.5], std[0.5, 0.5, 0.5]) ]) # 验证/测试只做 Resize 和归一化 val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.5, 0.5, 0.5], std[0.5, 0.5, 0.5]) ]) full_dataset datasets.ImageFolder(shapes_dataset, transformtrain_tf) # 按 7:2:1 划分固定随机种子保证可复现 torch.manual_seed(42) n_total len(full_dataset) n_train int(0.7 * n_total) n_val int(0.2 * n_total) n_test n_total - n_train - n_val train_set, val_set, test_set random_split(full_dataset, [n_train, n_val, n_test]) # 验证集要换成 val_tf这里用 Subset 包装时需重新设置 transform # 简单做法分别创建 dataset 再取索引或者用 DataLoader 的 collate 里不做增强 train_loader DataLoader(train_set, batch_size32, shuffleTrue, num_workers2) val_loader DataLoader(val_set, batch_size32, shuffleFalse, num_workers2) test_loader DataLoader(test_set, batch_size32, shuffleFalse, num_workers2)逻辑说明random_split直接按长度切分不保证类别均衡所以最好先统计每类数量再手动分层。Normalize的 mean 和 std 都设 0.5 是因为合成图背景接近白色实拍图也以浅色为主用 ImageNet 的均值反而会引入偏差。batch_size32在 224×224 输入下显存占用约 1.2GB6GB 显存的卡就能跑。参数说明RandomRotation(degrees15)比 30 更安全因为训练集里已经有合成时的 ±30° 旋转再叠加容易导致形状出界。ColorJitter的三个系数不要超过 0.3否则颜色失真会让形状边缘和背景混在一起。num_workers在 Windows 上设 0Linux 上设 2 到 4设太大反而拖慢小数据集的加载。3.2 一个四分类 CNN 的最小训练循环模型不用 ResNet自己搭一个 4 层卷积加全局平均池化就够了。参数量控制在 50 万以内推理时 CPU 单张图 8ms 左右。import torch.nn as nn import torch.optim as optim class ShapeNet(nn.Module): def __init__(self, num_classes4): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 16, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(16, 32, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(64, 64, 3, padding1), nn.ReLU(), nn.AdaptiveAvgPool2d(1) ) self.classifier nn.Linear(64, num_classes) def forward(self, x): x self.features(x) x x.view(x.size(0), -1) return self.classifier(x) device torch.device(cuda if torch.cuda.is_available() else cpu) model ShapeNet(num_classes4).to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-3) for epoch in range(30): model.train() running_loss 0.0 for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() # 验证阶段 model.eval() correct, total 0, 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) preds model(imgs).argmax(dim1) correct (preds labels).sum().item() total labels.size(0) print(fepoch {epoch1}, loss {running_loss/len(train_loader):.4f}, val_acc {correct/total:.4f})逻辑说明AdaptiveAvgPool2d(1)把 28×28 的特征图压成 1×1替代全连接层减少参数量。CrossEntropyLoss内部带 softmax不需要手动加。学习率 1e-3 配合 Adam 在 30 个 epoch 内基本收敛如果验证集准确率在第 15 个 epoch 后不再上升可以降到 1e-4 再跑 10 个 epoch。参数说明lr1e-3是 Adam 的常用起点如果 loss 震荡剧烈改成 5e-4。epoch30是针对 2000 张训练图的经验值图多了要加。验证时记得model.eval()和torch.no_grad()否则 BatchNorm 和 Dropout 会干扰结果。如果显存不够把batch_size降到 16同时学习率乘以 0.5。3.3 混淆矩阵告诉你哪两类在互相误判训练完在测试集上跑一遍输出混淆矩阵。四分类的混淆矩阵是 4×4行是真实标签列是预测标签。我见过最多的误判是星形和三角形——五角星的尖角在低分辨率下和三角形的顶点很像。如果星形被误判为三角形的比例超过 10%说明模型没有学到“内凹”特征。解决办法在训练集里增加星形的内凹角度变化或者把输入分辨率从 224 提到 256。from sklearn.metrics import confusion_matrix import numpy as np model.eval() all_preds, all_labels [], [] with torch.no_grad(): for imgs, labels in test_loader: imgs imgs.to(device) preds model(imgs).argmax(dim1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) cm confusion_matrix(all_labels, all_preds) print(cm) # 输出示例 # [[48 1 0 1] # [ 0 50 0 0] # [ 0 0 49 1] # [ 2 0 0 48]]逻辑说明confusion_matrix的第一个参数是真实标签第二个是预测标签。对角线是正确分类数非对角线是误判。如果某一列的和远大于对角线说明模型倾向于把其他类预测成这一类。参数说明test_loader的shuffle必须为 False否则标签和预测对不上。如果测试集太小比如每类不到 30 张混淆矩阵的波动会很大建议至少每类 50 张。4. 形状分类的避坑与排查那些让准确率卡在 0.7 的细节4.1 现象验证集准确率 0.99测试集掉到 0.6原因合成图和实拍图混在一起划分时同一张原图的增强版本跨了集。比如原图 A 的旋转版在训练集原图 A 的翻转版在测试集模型见过“类似”的图测试集自然虚高。解决按原图 ID 划分同一 ID 的所有增强版本只进一个集。如果合成图和实拍图都有先按来源分层再按 ID 划分。4.2 现象模型把圆形和正方形都预测成三角形原因背景色和形状填充色对比度太低边缘模糊卷积核学不到有效梯度。检查一下BG_RANGE和FG_RANGE是否有重叠。解决把背景限制在 220 到 255填充限制在 0 到 60确保灰度差至少 160。实拍图如果背景是灰色桌面换一张白纸垫底。4.3 现象训练 loss 降到 0.01 后不再下降但验证 loss 开始上升原因过拟合。四分类任务参数量超过 100 万就容易过拟合尤其是合成图纹理单一。解决把ShapeNet的最后一层卷积通道从 64 降到 32或者加nn.Dropout(0.3)在分类头之前。另外早停策略设patience5验证 loss 连续 5 个 epoch 不降就停。4.4 现象推理时单张图预测结果和批量预测不一致原因model.eval()没加或者输入归一化的 mean/std 和训练时不一致。检查推理代码里是否用了val_tf而不是train_tf。解决把预处理封装成一个函数训练和推理共用避免手写两套。另外ToTensor()会把像素值从 0-255 缩到 0-1如果推理时忘了这一步输入范围变成 0-255模型输出会完全乱掉。4.5 现象星形和三角形混淆严重调参无效原因星形的内凹特征在 224×224 下只有几个像素宽卷积核感受野不够。解决把输入分辨率提到 320×320或者在第一层卷积后用nn.MaxPool2d(2)之前加一个nn.Conv2d(3, 16, 5, padding2)增大感受野。另一个办法是数据层面生成星形时把内凹半径从r*0.4改成r*0.3让内凹更明显。5. 把模型塞进边缘设备ONNX 导出与量化后的精度验证训练完的 PyTorch 模型如果要部署到树莓派或 Jetson Nano直接装 PyTorch 太重。我一般导出成 ONNX再用 ONNX Runtime 做 INT8 量化。导出时注意opset_version选 11 或 13输入动态轴只保留 batch 维度。import torch.onnx model.eval() dummy_input torch.randn(1, 3, 224, 224).to(device) torch.onnx.export( model, dummy_input, shape_net.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}}, opset_version11 )逻辑说明dynamic_axes让 batch 维度可变推理时可以一次送多张图。opset_version11兼容大多数 ONNX Runtime 版本。导出后用onnxruntime加载对比 PyTorch 和 ONNX 的输出差异如果最大绝对误差超过 1e-4检查是否有不支持的自定义层。量化用 ONNX Runtime 的quantize_static需要准备 100 张校准图。校准集从训练集里随机抽覆盖四类。量化后模型大小从 2MB 降到 500KB推理速度提升约 2 倍但精度可能掉 1 到 3 个百分点。如果掉超过 5 个点说明校准集分布和测试集差异太大换一批校准图。验证量化模型时不要只看整体准确率要重新算一遍混淆矩阵。我遇到过量化后圆形召回率从 0.98 掉到 0.85 的情况原因是圆形的边缘像素在 INT8 下被截断。解决办法对圆形类做少量微调或者把量化改成per_channel模式。最后说个习惯每次导出 ONNX 后我都会用同一张测试图分别跑 PyTorch 和 ONNX打印 top-1 类别和置信度两者一致才继续往下走。这个检查花不了两分钟但能省掉后面几个小时的排查。希望帮到你。本文还有配套的精品资源点击获取