ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PyTorch颜色识别实战:从数据准备、CNN建模到训练评估全流程

PyTorch颜色识别实战:从数据准备、CNN建模到训练评估全流程 简介这是一份基于PyTorch实现的颜色识别项目代码面向需要完成毕业设计、期末大作业或课程设计的计算机相关专业学生也适合有一定Python基础、想通过实战入门深度学习的开发者。项目包含完整的数据处理、CNN模型定义、训练与检测流程代码注释详细新手也能读懂。压缩包共14个文件约6.93MB其中9个Python脚本覆盖主程序、图像转换、模型构建、训练与检测等核心模块另含预训练权重文件pth、环境依赖配置文件yml与txt、说明文档md等拿到后简单部署即可使用。目前已有210人学习下载。该高分项目功能完整、界面直观不仅演示了从数据准备到模型训练再到颜色识别的全流程还提供了可直接调用的检测脚本与权重读者既能快速复现识别效果也可参考其工程结构进行二次开发完成自己的课程任务或拓展应用场景。1. 用 PyTorch 做颜色识别为什么是课程作业的高分路径颜色识别听起来是入门级任务很多人直接用 OpenCV 的 inRange 写死几个 HSV 阈值就交差了。但课程作业拿高分的关键恰恰不在识别得准而在方案能不能讲清楚。硬编码阈值最大的问题是你说不出阈值为什么是 25 而不是 30模型没有任何可解释的学习过程。用 PyTorch 把颜色识别当作图像分类问题来处理数据准备、模型结构、损失函数、评估指标每一环都能正向解释报告里可以写满三页真实分析与实验。无论大作业要求识别固定色块、桌面物体还是简单场景这条路线都适用而且对 GPU 没有硬性要求CPU 也能跑完。接下来按数据准备、模型搭建、训练评估、提交前验证四个环节把关键代码、参数和常见坑位一次说透。2. 在 PyTorch 里构建颜色识别数据集先把归一化想清楚颜色识别大作业的常见做法是手工拍几百张照片标类别。这个方案在小型数据集上不是不行但样本数量少、光照不统一后面模型出现误判时很难定位是数据问题还是模型问题。我一般先做合成数据再混合少量真实样本。2.1 类别体系怎么定8 类比 20 类更适合作业先确定分类体系。常见颜色类别定在 8 类左右红、绿、蓝、黄、橙、紫、青、灰。类别太少显得工作量不够太多会出现粉红算不算红深蓝算不算青这类边界争议答辩时要花大量篇幅解释反而扣分。8 类是一个文档友好、演示稳定的范围。有一个容易踩的坑在 RGB 空间里直接做分类模型需要额外学习亮度解耦因为同样一种红色在暗光下 RGB 值可能是 (80, 0, 0)在强光下是 (220, 50, 50)。人眼识别颜色的核心是色调而不是 RGB 数值本身。所以数据准备阶段就转 HSV 空间更合理H 通道承载主色调S 和 V 分别承载饱和度和明暗模型学起来轻松报告里也能给出为什么 HSV 比 RGB 更适合颜色识别的分析。2.2 用脚本批量生成颜色样本避免手工标注合成数据的核心思路在 HSV 空间里按每个类别的中心色调生成色块然后叠加明度、饱和度抖动和噪声。生成脚本如下。import os import cv2 import numpy as np from tqdm import tqdm # 这里的 H 值范围是 0~179对应 OpenCV 的 HSV 表示 CATEGORY_HUE { red: 0, # 红在 OpenCV 中围绕 0 和 179 两个端点 orange: 15, yellow: 30, green: 60, cyan: 90, blue: 120, purple: 140, } def generate_patch(hue, size64): # 色调抖动 ±8模拟同类颜色的深浅差异 h int(np.clip(hue np.random.uniform(-8, 8), 0, 179)) s np.random.randint(80, 256) # 饱和度80~255 v np.random.randint(100, 256) # 明度100~255避免过暗 img np.full((size, size, 3), (h, s, v), dtypenp.uint8) rgb cv2.cvtColor(img, cv2.COLOR_HSV2RGB) # 加高斯噪声模拟传感器噪声 noise np.random.normal(0, 6, rgb.shape).astype(np.float32) return np.clip(rgb noise, 0, 255).astype(np.uint8) def build_dataset(root, per_class2000): for cls, hue in CATEGORY_HUE.items(): os.makedirs(os.path.join(root, cls), exist_okTrue) for i in tqdm(range(per_class), desccls): cv2.imwrite(os.path.join(root, cls, f{i:05d}.png), generate_patch(hue)) build_dataset(./data/color_train)参数说明CATEGORY_HUE中的 H 值用的是 OpenCV 范围不是数学常用的 0~360写错这个会直接导致所有蓝色样本变成绿色。色调抖动幅度 ±8 是我测试后比较稳的选择改成 ±15 会提高模型对偏色的容忍度但训练也会更难收敛。每类 2000 张、8 类共 16000 张的训练集在 CPU 上训练一个小型 CNN 只需几分钟。提示合成数据训练的模型直接放到真实照片上会有分布差异建议留出 10% 的真实拍照样本做混合微调答辩时这是数据增强策略的加分项。2.3 Dataset 定义与 Normalize 参数的两种选择PyTorch 的torchvision.datasets.ImageFolder可以直接读取文件夹结构的数据集前提是目录格式为label/图片.jpg。归一化参数这里有两个流派用 ImageNet 的均值 (0.485, 0.456, 0.406) 和方差 (0.229, 0.224, 0.225)用自计算的均值方差或用对称的 0.5 归一化颜色识别场景中如果模型是从零训练的小型 CNN用 0.5 归一化更合理。ImageNet 的统计量来自自然图像对纯色块反而是一种偏差。如果后面走迁移学习加载 ResNet 预训练权重那必须换回 ImageNet 参数否则第一层输入分布与预训练假设不匹配。from torch.utils.data import DataLoader from torchvision import datasets, transforms train_tf transforms.Compose([ transforms.RandomRotation(15), transforms.ColorJitter(brightness0.3, contrast0.2), transforms.Resize((64, 64)), transforms.ToTensor(), transforms.Normalize(mean[0.5, 0.5, 0.5], std[0.5, 0.5, 0.5]), ]) val_tf transforms.Compose([ transforms.Resize((64, 64)), transforms.ToTensor(), transforms.Normalize(mean[0.5, 0.5, 0.5], std[0.5, 0.5, 0.5]), ]) train_ds datasets.ImageFolder(./data/color_train, transformtrain_tf) val_ds datasets.ImageFolder(./data/color_val, transformval_tf) train_loader DataLoader(train_ds, batch_size64, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size64, shuffleFalse, num_workers4) print(类别映射表, train_ds.class_to_idx)逻辑说明ColorJitter在训练时随机改亮度与对比度等价于扰动 HSV 里的 V 通道让模型不依赖固定亮度Resize((64, 64))统一输入尺寸64×64 对颜色识别足够太小会丢失颜色过渡信息太大则增加计算量。验证集的shuffleFalse保证评估时样本顺序固定指标可复现。归一化方案mean / std适用场景ImageNet 统计量0.485 / 0.229 等三组迁移学习必须沿用对称归一化0.5 / 0.5从零训练小型 CNN按数据集计算自行统计真实照片混合训练3. 模型搭建轻量 CNN 与 ResNet 迁移学习的选型依据颜色识别属于低层视觉任务核心特征都在前几层卷积里。这意味着不需要堆叠很深的网络几十层模型在颜色数据上反而容易过拟合。3.1 为什么 3 层卷积就够用一个颜色块的主要特征表现为某个区域的主色调这本质上是通道维度的统计特征不需要复杂的空间纹理抽象。3 层卷积足以提取颜色分布信息因为第一层卷积核就会学到 RGB 通道间的加权组合等价于在通道层面做颜色挑选。网络再深特征图里主要保留的是边缘和形状结构与颜色任务的相关性反而变弱。3.2 用 PyTorch 定义一个轻量 ColorNetimport torch import torch.nn as nn class ColorNet(nn.Module): 3通道输入输出颜色类别数 def __init__(self, num_classes8): super().__init__() self.features nn.Sequential( # 第一层提取局部颜色组合输出 32 个特征图 nn.Conv2d(3, 32, kernel_size3, padding1), nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 32×32 # 第二层加深通道压缩空间 nn.Conv2d(32, 64, kernel_size3, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 16×16 # 第三层全局平均池化替代 Flatten nn.Conv2d(64, 128, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.AdaptiveAvgPool2d(1), ) self.classifier nn.Linear(128, num_classes) def forward(self, x): x self.features(x) x torch.flatten(x, 1) return self.classifier(x) model ColorNet(num_classes8) total_params sum(p.numel() for p in model.parameters()) print(f参数量{total_params / 1e3:.1f}K)代码说明BatchNorm2d在颜色数据上尤其重要因为颜色输入的值域波动比自然图像更大批归一化能稳定每层输出的分布。AdaptiveAvgPool2d(1)把 16×16×128 的特征压成 128 维向量相比直接Flatten能保留更多全局信息也避免了全连接层参数量爆炸。这套结构参数量约 0.2MCPU 上训练很快报告中写出这个数字能体现你关注模型复杂度。3.3 迁移学习对比实验ResNet18 的替换分类头大作业如果想体现模型对比可以加一个 ResNet18 微调的实验。做法是加载预训练权重冻结所有卷积层只训练最后的全连接分类头。import torchvision.models as models def build_resnet18(num_classes8): model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) for param in model.parameters(): param.requires_grad False # 冻结特征提取层 in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes) return model resnet build_resnet18(8) trainable_params sum(p.numel() for p in resnet.parameters() if p.requires_grad) print(f可训练参数量{trainable_params / 1e3:.1f}K)参数说明weightsmodels.ResNet18_Weights.IMAGENET1K_V1是 torchvision 较新版本的写法旧版pretrainedTrue会报 DeprecationWarning。requires_gradFalse含义是不参与反向传播更新训练时只优化新加的全连接层。这里有个典型案例如果坚持用 0.5 归一化喂给 ResNet预训练权重对输入分布的假设被破坏最终准确率会比小 CNN 还低。迁移学习必须配合 ImageNet 归一化。模型参数量训练时间(CPU)精度特点ColorNet(3层卷积)约 0.2M3-5 分钟纯色块上 99%ResNet18 微调分类头可训练约 0.4M15-20 分钟真实照片更稳4. 训练与评估损失函数、学习率调度和混淆矩阵训练环节的大作业价值在于能解释每个超参数为什么这么设。很多同学的报告写loss 降低了但没有说明用的是什么优化器、做了哪些调度、指标为什么这么选。4.1 损失函数与类别不平衡处理颜色识别默认用nn.CrossEntropyLoss。如果某些类别的样本数偏少比如紫色在合成数据里生成得少就要用类别加权。常见做法是从训练集统计类别数量计算权重。class_counts torch.tensor([1600, 1550, 1500, 1650, 1400, 1200, 1450, 1580], dtypetorch.float32) weights class_counts.max() / class_counts criterion nn.CrossEntropyLoss(weightweights) print(各类别损失权重, weights.numpy())逻辑说明max / class_counts的计算方式让样本少的类别获得更大权重反向传播时少数类的梯度被放大模型不会为了总体准确率而忽视稀有类别。如果各类别数量均匀权重可以直接不传但把统计过程留在代码里会让答辩更有说服力。4.2 训练主循环模板零梯度、反向传播与模型保存import torch.optim as optim device torch.device(cuda if torch.cuda.is_available() else cpu) model ColorNet(num_classes8).to(device) optimizer optim.Adam(model.parameters(), lr1e-3, weight_decay1e-4) scheduler optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.5) def train_one_epoch(model, loader, criterion, optimizer): model.train() total_loss, correct, total 0.0, 0, 0 for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() # 清空上一轮梯度 outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() * imgs.size(0) correct (outputs.argmax(1) labels).sum().item() total labels.size(0) return total_loss / total, correct / total for epoch in range(30): loss, acc train_one_epoch(model, train_loader, criterion, optimizer) scheduler.step() print(fEpoch {epoch1:02d} | loss {loss:.4f} | acc {acc:.4f}) torch.save(model.state_dict(), f./checkpoints/colornet_{epoch1:02d}.pth)参数说明optimizer.zero_grad()不可省略PyTorch 的梯度是累积的不清空会让多个 batch 的梯度叠加训练曲线剧烈震荡。lr1e-3是 Adam 对中小型网络的常用起点如果 loss 下不去改 3e-4 更稳。StepLR(step_size10, gamma0.5)表示每 10 个 epoch 学习率减半让后期在损失面平坦区域做更精细的收敛。每轮保存权重是为了可以在验证集指标回退时回滚这个细节在演示时能体现工程能力。4.3 评估指标准确率之外必须有混淆矩阵颜色识别很容易做到 98% 以上准确率但这种指标掩盖了关键信息哪些颜色对在混淆用混淆矩阵一眼就能看出橙色和黄色是否边界重叠。from sklearn.metrics import confusion_matrix def evaluate(model, loader, class_names): model.eval() preds, labels [], [] with torch.no_grad(): for imgs, targets in loader: imgs imgs.to(device) out model(imgs).argmax(1).cpu().numpy() preds.extend(out.tolist()) labels.extend(targets.numpy().tolist()) cm confusion_matrix(labels, preds) for i, name in enumerate(class_names): wrong [(class_names[j], cm[i][j]) for j in range(len(class_names)) if i ! j and cm[i][j] 0] if wrong: print(f{name} 误判为{wrong}) return cm cm evaluate(model, val_loader, val_ds.classes)这段代码核心在最后一层判断逻辑它把每一类被误判成哪些类别打印出来。如果橙色→黄色出现大量样本说明训练集的色调抖动跨度太大或者归一化后两种颜色的距离太近解决办法是减小色相抖动到 ±6 或增加橙色类别的样本数。这个指标驱动数据调整的闭环是大作业拿高分的核心方法论。超参数推荐值说明batch_size64CPU 训练时 32 也可以初始学习率1e-3Adam 默认规模下最稳weight_decay1e-4L2 正则防过拟合step_size10每 10 轮减半gamma0.5学习率衰减系数5. 提交前把能跑变成高分的三个关键验证课程作业评分看的不只是代码能不能跑通更是方案是否经得起质疑。以下是提交前必须做的三个验证。5.1 保存混淆矩阵图并挑出错误样本把混淆矩阵用 matplotlib 画出来保存成图片放进报告。再从验证集里挑出 2 到 3 个误判样本把原图、预测类别、预测置信度打印到一张图上。这个操作能让答辩老师直观看到模型在哪些地方有限度而不是只会报 99% 准确率。import matplotlib.pyplot as plt def visualize_error(model, loader, class_names, num_samples3): model.eval() shown 0 with torch.no_grad(): for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) probs torch.softmax(model(imgs), dim1) preds probs.argmax(1) for i in range(len(labels)): if preds[i] ! labels[i] and shown num_samples: top_p, top_c probs[i].max(0) plt.figure() plt.imshow(imgs[i].permute(1, 2, 0).cpu().numpy() * 0.5 0.5) plt.title(f真实: {class_names[labels[i]]} | f预测: {class_names[preds[i]]} | f置信度: {top_p:.3f}) plt.axis(False) plt.savefig(f./error_{shown}.png) shown 1逻辑说明显示图像前要做* 0.5 0.5的反归一化否则训练时的归一化会让图像显示成一片灰色。置信度从 softmax 输出里取最大值保留这个数字可以用于分析模型对边界色的处理方式是确定性错误还是低置信度可疑判断。5.2 额外跑一组边界色测试在训练集之外单独生成一组边界色样本比如放在红色和橙色中间的 25 度色调测试模型是否会对这类样本给出分散的概率分布而不是强行判定。合理的模型应当输出橙色 0.55红色 0.30黄色 0.15这样的软分布这代表它知道这个颜色接近边界而不是把边界色当独立类别。将结果做成一个三个小图横向排列的对比图报告中放一张是加分的常见做法。5.3 训练曲线与 README 的完整链条最后把每个 epoch 的训练 loss、验证准确率画在同一条图上加上第三节的混淆矩阵图整个大作业的代码和数据就构成了完整链路。README 中写明环境依赖、数据生成命令、训练入口和评估入口让助教按步骤走一遍就能复现结果。颜色识别虽然任务简单但整套方案体现的是工程完整性这才是高分项目真正的得分点。本文还有配套的精品资源点击获取
返回列表