每到毕设选题季总会有学生跑来问我深度学习方向的题目选什么才不容易翻车。聊得多了我发现大家真正担心的其实不是题目大小而是两件事能不能讲清楚实现逻辑能不能跑出看得见的结果。用 Python 写一个基于 CNN 卷积神经网络的模型去识别不同衣服颜色恰好就是这样一个“上下限都很清晰”的题目——它不需要你堆复杂的网络结构也不需要顶配显卡但过拟合、数据不均衡、光照干扰、模型评估这些深度学习里的典型问题它一个都不会少。这篇文章按我实际辅导学生完成这个项目的推进顺序把完整技术路线、调参经验和踩坑记录整理出来。这份记录适合几类人拿它当毕业设计题目的同学、想用图像分类练手的机器学习初学者以及需要快速交付可视化演示的开发同学。文中的代码基于 Python 和 PyTorch 实现网络主体是 CNN 卷积神经网络数据采用公开服装图库加自拍样本的混合方式任务设定为根据衣服主色完成多分类。下面先从选题逻辑讲起再拆数据、拆网络结构、拆训练过程最后给出一个可以直接复现的演示方案。1. 选题逻辑与项目边界颜色识别到底难在哪还没动手写代码的人最容易把“识别衣服颜色”当成一个送分题。人眼看颜色确实是本能红色就是红色蓝色就是蓝色。可一旦把这件事交给机器学习模型问题就变得不简单数码相机拍出来的 RGB 值受环境光照影响非常大同一件白色 T 恤在暖黄灯光下拍出来偏橙在阴天窗外光下拍出来偏蓝。不同面料的材质特性也会改变颜色表现丝光棉和牛仔布对光线的反射方式完全不同。再加上衣服常常带花纹、拼接、渐变想让模型识别出“主色”本身就需要先对整幅图的颜色信息做归纳和空间压缩。所以这个项目的核心难点不是“模型能否学会分类”而是“颜色这个语义概念能否稳定地被学到”。在项目设计阶段我会先把边界定清楚输入为一件衣服的正面全身图或局部图输出为颜色类别类别从最常见的色系中选取比如黑色、白色、灰色、红色、橙色、黄色、绿色、蓝色、紫色、粉色这 10 类。如果图片里有多件衣服或复杂背景就先裁剪或更换样本。这种做法不是偷懒而是让实验变量可控——论文里才能讲清楚模型到底学到了什么。1.1 为什么说它是典型的“不难不简单”题目从教学角度看这个题目的难度梯度非常合理。基础部分是数据整理、模型训练、指标评估进阶部分可以加入特征图可视化、光照鲁棒性分析、类别混淆归因任何一部分都能写出有血有肉的章节而不是空谈理论。相比人脸识别、目标检测这类依赖大规模数据和重型框架的题目衣服颜色识别对硬件要求低单卡训练一轮只要十几分钟学生能反复实验这比一次训练跑几小时甚至几天的题目友好太多了。1.2 类别范围要先定死别一上来搞几十类我也见过想直接做二三十类细分的把浅蓝、天蓝、藏青、深蓝全部拆开。我的建议是第一次迭代千万别这么干。类别越细类间差异越小模型越容易学会“亮度差异”而不是“颜色本质差异”。更合理的做法是先跑通大类流程后续再逐步细分同时把容易混淆的类别合并成一组。这也是很多公开颜色分类任务只分 8 到 12 个基础色系的原因在控制任务难度的同时也保证人眼能对最终结果做出合理评判。2. 数据集与预处理同一件衣服在不同光线里是两种颜色数据问题是这个项目里最先出现的坑。公开的服装数据集通常带类别、季节、风格等标注但不一定直接提供颜色标签。我的做法是混合策略先从公开服装图库中筛选一批背景干净、衣服占比高的图片按主色人工归类再补拍一些不同灯光条件下的实物照片模拟真实场景里的颜色偏移。两部分合起来训练集约 12000 张验证集约 2000 张并尽量让各类别数量均衡。筛选时有个原则特别容易被忽略不要只挑“一眼就能看出颜色”的图。如果所有训练图都在棚拍白光下拍的模型学到的是很窄的颜色映射部署到自然光环境会大幅退化。因此我会故意保留部分暖光、逆光、阴影样本甚至单独做一个“光照扰动子集”放到验证集里检验鲁棒性。否则你训练出的模型可能只是背下了考试环境的答案换个场景就露馅。2.1 标注规则和“多色样本”怎么处理标注策略我们采用两阶段规则先看衣服整体如果超过 60% 的面积属于同一色系就按主色标注如果不满足就标成“多色”并从训练集中剔除。这条规则看上去粗暴但对训练很有价值。因为 CNN 最终输出的是全图特征如果一张图里又红又蓝标注成红色会让模型产生严重的语义冲突。把多色样本剔除比强行让模型学会“挑重点颜色”要安全很多收敛速度也更快。如果你不想人工筛也可以用程序辅助筛选先计算图片在 HSV 空间的色相直方图如果主色相区间占比达不到 60%就自动归入待人工复核队列。当时我写了个脚本批量统计直方图筛掉了一大批拼接款卫衣和条纹 T 恤效果立竿见影比肉眼逐张看效率高出很多。2.2 颜色空间转换为什么我建议从 RGB 切到 HSV预处理阶段除了常规的 resize 到 224x224最重要的一个技巧是颜色空间的转换。RGB 三个通道耦合性强对亮度变化特别敏感HSV 颜色空间把色相H、饱和度S、明度V拆开其中 H 通道更接近人对颜色的感知。实际训练时我会把 RGB 转 HSV再把三个通道归一化后作为网络输入让网络自行学习三者的权重。实验显示仅这一项改动就能让深色类别的稳定识别有肉眼可见的改善模型终于不用被 RGB 通道的强耦合牵着走了。import cv2 import numpy as np import torch def rgb_to_hsv_tensor(pil_img): rgb np.array(pil_img.convert(RGB)) hsv cv2.cvtColor(rgb, cv2.COLOR_RGB2HSV).astype(np.float32) hsv hsv / 255.0 # 形状从 (H, W, 3) 转为 (3, H, W)方便送入网络 tensor torch.from_numpy(hsv).permute(2, 0, 1) return tensor这里要特别提醒通用图像增强手段在这个任务里必须慎用。随机亮度、随机对比度、颜色抖动在一般分类任务里很好用但在颜色识别任务里会直接制造标签冲突。你调亮一张红色卫衣 30%它还是红色可你把一张淡蓝色衬衫调成高饱和、高亮度它在视觉上就可能接近紫色了。标签没变特征却变了模型自然学出一套混乱的映射。我实际训练时只保留水平翻转、随机缩放裁剪和极小范围的明度扰动色相通道基本不动。3. 卷积网络结构调整小数据集下到底该加深还是该加宽网络结构选择上很多人的第一反应是拿预训练模型微调。但如果这是毕业设计我通常建议至少自己搭一版 CNN 跑通再用预训练模型做对照。原因不是预训练模型不好而是全程黑盒微调的话答辩时很难讲清楚每一层在干什么。自己搭的网络虽然精度可能低几个点但可控性强、可解释性好参数量和计算量都能直接算出来写成公式。真到了项目后期想提精度再引入迁移学习也不迟。针对这个任务我最终选用的结构是4 个卷积块每块包含两层 3x3 卷积、一个批归一化、一个 ReLU 激活和一个最大池化最后接全局平均池化和两层全连接分类头。通道数从 32 起步每个块翻倍最终到 256。这个思路类似小型 VGG但通道数更克制整体参数量在 1200 万上下用一块 6GB 显存的显卡训练完全够用。import torch.nn as nn class ClothColorCNN(nn.Module): def __init__(self, num_classes10): super().__init__() self.features nn.Sequential( self._make_block(3, 32, 2), self._make_block(32, 64, 2), self._make_block(64, 128, 2), self._make_block(128, 256, 2), ) self.pool nn.AdaptiveAvgPool2d(1) self.classifier nn.Sequential( nn.Dropout(0.3), nn.Linear(256, 128), nn.ReLU(inplaceTrue), nn.Linear(128, num_classes), ) def _make_block(self, in_ch, out_ch, num_conv): layers [] for i in range(num_conv): layers.append(nn.Conv2d(in_ch if i 0 else out_ch, out_ch, kernel_size3, padding1)) layers.append(nn.BatchNorm2d(out_ch)) layers.append(nn.ReLU(inplaceTrue)) layers.append(nn.MaxPool2d(kernel_size2, stride2)) return nn.Sequential(*layers) def forward(self, x): x self.features(x) x self.pool(x) x x.view(x.size(0), -1) x self.classifier(x) return x3.1 为什么用 3x3 堆叠而不是更大的卷积核很多同学写代码时喜欢直接把卷积核改成 5x5 或 7x7理由是“感受野更大”。但感受野大不等于效果好。两个 3x3 卷积堆叠等价于一个 5x5 卷积的感受野但参数量更少而且中间多了一次非线性变换表达能力反而更强。在小规模数据集上这相当于用更少的参数获得同级别的能力过拟合风险也会更小。如果你担心丢细节可以在第二个卷积块后用膨胀卷积扩大感受野但这步优化一般放到实验后期再做。3.2 通道数、全局平均池化和 Dropout 的取舍通道数设计同样有讲究起步用 32 而不是 64 或 128是为了控制模型容量在数据量允许的范围内。如果训练集只有一万多张图通道数翻倍往往换来的是过拟合训练集准确率冲到 99%验证集却上不去。通道数太小又拟合不足Loss 降不到理想值。以 32 起步224x224 输入经过四层池化后特征图变成 14x14空间分辨率依然保留颜色的局部信息能顺利传导到全局特征。全局平均池化在这里比直接展平更有优势。传统做法是把特征图展平再接全连接参数量会爆炸全局平均池化把每个通道压缩成一个数值让最后一层卷积被迫学出更有区分度的空间特征同时明显减小网络体积。全连接部分只保留一个隐藏层中间加 Dropout 0.3就足够应付 10 分类任务。结构再深在这个数据规模下多半是吃力不讨好。4. 训练调参实录Loss 曲线不听话时的排查顺序训练过程是整个项目里最容易心态崩掉的阶段。第一次跑实验时训练 Loss 降到 0.3 左右就停住不动验证集准确率卡在 84%。当时第一反应是模型不够强差点去换更大的网络。后来冷静下来按顺序排查问题出在数据输入链路——我们之前开了颜色抖动增强幅度还比较大等于给模型喂了互相矛盾的标签。关掉颜色抖动只保留轻度明度扰动同一结构下验证集准确率直接跳到 88.5%。这个教训后来反复讲给其他学生遇到瓶颈先查数据再查超参最后才怀疑模型结构。4.1 优化器和学习率的搭配训练时我先用 Adam初始学习率 3e-4batch size 64。Adam 收敛快、对学习率不敏感适合快速验证想法。到了训练后期我会切到带动量的 SGDmomentum 0.9学习率降到 1e-3并用余弦退火或阶梯式衰减。原因是 Adam 在接近局部最优时容易震荡SGD 在精细搜索阶段更稳最终精度常常高零点几个百分点。如果不想中途切优化器也可以直接用 AdamWweight decay 设 1e-4过拟合压力会小一些。下面这张表是我在不同配置下跑出来的直观感受供参考配置适合场景优点需要注意Adamlr3e-4快速验证收敛快参数少后期易震荡SGD momentumlr1e-3后期精调稳定精度更高收敛慢对 lr 敏感AdamWwd1e-4有轻微过拟合稳定正则效果好超参数略多4.2 早停、学习率调度和 Checkpoint推荐一个非常实用的组合用验证集 Loss 做监控连续 5 个 epoch 不下降就把学习率乘 0.3连续 10 个 epoch 不下降就停止训练并恢复最佳权重。这段逻辑实现不复杂但效果很好能自动适应不同数据集的学习节奏。另外每个 epoch 结束后务必保存 Checkpoint内容至少包括模型权重、优化器状态、当前 epoch 数、验证集指标防止训练中途断掉后一切从头再来。best_val_loss float(inf) patience 0 for epoch in range(max_epochs): train_loss train_one_epoch() val_loss validate() if val_loss best_val_loss: best_val_loss val_loss patience 0 torch.save(model.state_dict(), cloth_color_best.pth) else: patience 1 if patience 10: break4.3 显存不够混合精度是你的救星显存只有 4GB 的话开启混合精度就能把 batch size 提上去训练时间也能省一半左右。PyTorch 里通过 torch.amp 模块调用 FP16 前向计算和梯度缩放不需要改模型结构。第一次跑混合精度时心里有负担担心数值不稳实测下来只要统一用 GradScaler 做反向传播精度和 FP32 几乎没差别。如果你也是新手建议先在小数据子集上验证稳定再上全量数据。5. 混淆矩阵与错误样本黑/深蓝为什么总是认错模型跑完不能只看 90% 的整体准确率就收工。把验证集预测结果做成混淆矩阵逐类分析错误模式这一步才是真正能区分“会调包”和“会做项目”的分水岭。我们最终版本的模型整体准确率约 90.7%打开混淆矩阵后问题立刻暴露黑色和深蓝色互认错误率最高黑色样本里有 11% 被预测成深蓝色深蓝色样本里有 8% 被预测成黑色紫色和蓝色也常有混淆。这个现象并不难理解黑色本来就不是某个固定波长而是低明度下人眼对多种深色区的统一称呼深蓝牛仔布在照片里的数值表现与纯黑差异极小。5.1 按行归一化的查全率才是真实短板把混淆矩阵按行归一化后每一项才是该类别的“查全率”。下表是我整理出的重点错误流向只看整体准确率永远看不到这些细节真实类别查全率主要误判去向黑色89.2%深蓝色 11%深蓝色91.0%黑色 8%紫色87.5%蓝色 7%橙色92.3%红色 5%粉色93.1%紫色 4%看到这些数字我反而踏实因为这说明模型错误不是随机乱猜而是有规律的。针对黑/深蓝混淆我试了两种改进一种是在 HSV 空间里给明度通道更高权重让模型更重视亮度另一种是调整类别定义把深蓝样本中那些和黑色几乎无法区分的图片筛选掉。第一种提升有限第二种效果更明显但也说明一个事实当类别边界连人都很难统一口径时模型产生困惑很正常调整标签定义的收益往往大于继续调参。5.2 错误样本里的“合理错误”比指标更值得写进论文逐张翻看错误样本也很有价值尤其要关注那些模型“错得有理”的图。比如红色卫衣带白色拼接袖口模型预测成了粉色黄色衬衫在强光高光区几乎变白模型预测成了白色。这些错误都不是模型笨而是光照和纹理干扰了主色判断。我的处理办法是继续筛选多色拼接面积过大的样本并补充带高光的训练图。两轮迭代后整体准确率从 90.7% 升到 92.4%黑/深蓝错分率明显下降。这里我想强调别被准确率绑架。答辩时与其报一个虚高的数字不如展示你对错误样本的理解。如果能解释黑色为什么容易和深蓝混淆并能给出成型的改进方案这比把准确率从 92% 硬刷到 93% 有用得多。我也见过学生为了刷分偷偷删掉困难样本这种做法一旦被追问就会穿帮整个项目的可信度都会崩掉。6. 从模型到答辩 Demo用 Streamlit 快速封装一个可演示的颜色识别器模型再准如果只停留在训练脚本里答辩现场也撑不起场面。我的习惯是让演示至少覆盖两件事上传任意一张衣服图片模型在几秒内给出颜色分类和置信度能展示预处理过程和特征图证明模型关注的是衣服区域。基于这个目标用 Streamlit 做一个轻量页面把所有推理逻辑封装成后端函数前端只负责上传和结果展示几分钟就能跑起来。import streamlit as st import cv2 import numpy as np import torch from PIL import Image class_names [黑色, 白色, 灰色, 红色, 橙色, 黄色, 绿色, 蓝色, 紫色, 粉色] st.cache_resource def load_model(): model ClothColorCNN(num_classes10) model.load_state_dict(torch.load(cloth_color_best.pth, map_locationcpu)) model.eval() return model def preprocess(pil_img): pil_img pil_img.resize((224, 224)) rgb np.array(pil_img.convert(RGB)) hsv cv2.cvtColor(rgb, cv2.COLOR_RGB2HSV).astype(np.float32) / 255.0 tensor torch.from_numpy(hsv).permute(2, 0, 1).unsqueeze(0) return tensor st.title(衣服颜色识别 Demo) uploaded st.file_uploader(上传一张衣服图片, type[jpg, png, jpeg]) if uploaded is not None: img Image.open(uploaded).convert(RGB) st.image(img, caption原图, use_column_widthTrue) tensor preprocess(img) with torch.no_grad(): prob torch.softmax(load_model()(tensor), dim1)[0] top_idx int(prob.argmax()) st.write(f预测结果{class_names[top_idx]}置信度{prob[top_idx]:.2%})6.1 两个不起眼但致命的细节第一个是convert(RGB)不能省。很多手机图片带 EXIF 方向信息直接用 PIL 打开再 Resize方向可能错乱先统一转 RGB 再进统一路径能避免很多奇怪问题。第二个更关键推理时的输入预处理必须和训练时完全一致。如果你在训练里把 RGB 转成了 HSV推理里却用 RGB 归一化模型看到的分布就完全不一样精度会掉一大截。上面代码就是统一走 HSV 路径的这一点我在带学生的过程中被反复验证过。6.2 特征图可视化是答辩加分项想展示更“硬核”的内容可以在模型 forward 时把 features 部分的输出单独存下来从四层卷积块各取一张特征图用 torchvision.utils.make_grid 拼成大图再用 cv2.applyColorMap 上色。演示页面上并排显示原图、浅层特征图、深层特征图就能直观讲清网络逐层提取边缘、颜色块、面料纹理和整体轮廓的过程。评审老师看到这种可视化通常比看到一个准确率数字更愿意给好评。部署上本地演示用一个st.file_uploader就够用。如果要封装成接口把推理函数直接包成 FastAPI 接口即可前端框架随意。必须注意的是模型文件、依赖版本、CPU 或 GPU 环境要提前锁定用 requirements.txt 固定版本别等答辩当天才发现依赖升级后行为变了。其实整个项目做下来我最深的体会是衣服颜色识别看起来“太简单”真做完才知道它不简单但这种不简单恰恰是学习收益所在。它的边界清晰数据可控结构透明每一步都能被合理解释。每次带完这类项目我都会跟学生强调同一句话你的目标不是做一个全世界最准的颜色分类器而是用一条完整、可信的实验链路证明你理解从数据到模型再到评估的每一环并能根据错误模式做出有效决策。按这个思路走下去这题目在毕设中拿到亮眼成绩是很稳的。如果你后面想继续扩展我很推荐做粗细粒度结合的层级分类第一层判断颜色大类第二层针对易混淆的深色小类做二次判别结构不复杂但很有可讲性能作为论文的一个亮点。数据采集上也可以继续积累不同光源、不同场景、不同材质的样本把颜色识别系统的鲁棒性越做越厚。还有一个小技巧是每轮实验都随手记录配置和指标整理成简短实验日志写论文时你会发现这些记录比任何理论都经得起推敲。这些经验等你有时间了可以慢慢在实践中验证。