多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

基于机器学习的人脸识别大作业实战:从数据预处理到SVM分类器

基于机器学习的人脸识别大作业实战:从数据预处理到SVM分类器 简介一套基于Python机器学习实现的人脸识别大作业源码包面向需要完成机器学习课程设计、毕业设计或入门计算机视觉实战的Python学习者。压缩包约3.52MB集中了照片与视频两大场景下的完整人脸识别流程涵盖照片性别识别、视频人脸识别、人脸与眼睛检测、视频性别识别等多个可独立运行的功能模块并配有神经网络模型文件、工程代码说明文档以及运用BP神经网络实现性别检测的工作报告。各模块分工明确照片类任务可验证单张图像处理视频类任务则覆盖实时帧读取与识别便于按需调用和二次开发工程代码说明文档详细梳理了项目结构、运行环境与调用方法BP神经网络工作报告则从传统机器学习角度补充了算法原理有助于读者对比不同技术路线。目前已有1449人浏览学习整套资源体量虽小但内容覆盖面广既能作为课程大作业的直接参考也能帮助初学者理解从数据准备、模型加载到结果输出的典型机器学习实践流程还可在此基础上扩展其他面部分析任务。1. 拿到“基于python机器学习实现的人脸识别大作业源码课程报告项目说明.zip”你该怎么用这类压缩包是机器学习课程设计里最常见的交付形态一份能跑通的 Python 人脸识别程序、一份用来应付查重和答辩的课程报告、一份让别人能接手的环境说明。我的建议是别急着双击运行先把它当成一条完整流水线来看人脸检测、人脸对齐、特征提取、分类器识别四个环节缺一不可。真正决定大作业能拿什么档次的往往不是模型层数而是数据预处理到不到位——预处理严格了SVM 也能在班级数据集上跑到 95% 以上预处理粗糙再贵的网络也救不回来。适合三类人读被课程设计 deadline 追着跑的学生、想让简历里多一个“能讲清楚”的计算机视觉项目的人、以及在做门禁考勤类产品预研的开发者。2. 人脸识别大作业的路线选择为什么课设该走“检测到分类”这条线2.1 把人脸识别拆成四步检测、对齐、特征、分类很多人一上来就找“人脸识别算法”然后陷入了计算机视觉和机器学习到底有什么区别的困惑里。实践中一个能用机器学习理论解释的人脸识别系统不是单一算法而是四段流水线。第一步是人脸检测解决“脸在哪”的问题。常见工具是 OpenCV 的 Haar Cascade或者更稳一点的 MediaPipe、RetinaFace这一步输出的是人脸边界框坐标。第二步是人脸对齐把检测到的脸通过仿射变换摆正统一眼睛位置和面部尺度这是整套系统里最容易被跳过的环节也是识别率波动的最大来源。第三步是特征提取把一张图像变成一串固定长度的数值向量早期用 LBP、HOG现在主流是预训练卷积网络输出 embedding。第四步是分类或检索训练一个机器学习分类器或者拿特征向量去库里比对相似度最终回答“这张脸是谁”。把流程拆开有个额外好处课程报告每一章都能对应一个独立实验每个环节都能单独出图表和指标答辩时老师问到哪里你都能接上。很多同学把“人脸识别”理解成一个端到端模型等到报告里要写原理公式时发现全是黑匣子那才是真难受。2.2 常见实现路线对比LBPH、特征分类器、端到端网络我把市面上课设常用的三条路线做过一次对比结论挺明确题目标着“机器学习”的最优解是“手工特征或预训练特征 sklearn 分类器”而不是堆一个深度学习网络。实现路线特征来源分类/匹配方式适用场景课设适配度LBPH局部二值模式直方图OpenCV 内部近邻匹配小规模人脸库、快速演示中代码最短但公式少、没体现机器学习HOG/手工特征 SVMHOG 或 LBP 直方图sklearn 的 SVC、MLP班级考勤、门禁机原型高覆盖机器学习核心知识点预训练 CNN 分类器MobileNet/ResNet embeddingsklearn 的 SVM、随机森林数据量小但要求高鲁棒性高兼顾效果和可解释性端到端深度学习网络自己学Softmax 分类数据量大、有 GPU低偏离课程主题报告难写深LBPH 看起来最省事几行代码就能跑但它在 OpenCV 内部做的是近邻距离比对严格说跟你机器学习课上学的东西关系不大。端到端深度网络效果确实最好但人脸数据集通常只有几百张网络训练容易过拟合而且答辩时被问到“反向传播推导”会很痛苦。中间两条路线把“特征工程”和“分类器设计”分开正好落在机器学习课程的核心章节上报告能写的内容也最充实。2.3 选型的三个约束可解释、能复现、能写进报告给课程设计选技术路线跟给生产项目选型不太一样。生产项目看效果和运维成本课设更多看三点约束。第一是可解释。SVM 的软间隔目标函数、RBF 核的 gamma 参数、交叉验证选参这些在周志华的《机器学习》里都有完整章节写进报告是名正言顺的。换成深度网络报告里只能写“用了某预训练模型”原理部分几乎凑不出字数。第二是能复现。课设环境往往是室友的笔记本、实验室的公用台式机没 GPU 是常态。我一般要求代码在纯 CPU 机器上十分钟内能跑完一轮训练这个约束直接把大网络淘汰掉了。第三是能写进报告。课程报告需要实验图表sklearn 的混淆矩阵、GridSearchCV 的验证曲线都是现成的截图就能用。如果选端到端方案Loss 曲线之外就没什么好画的报告厚度撑不起来。所以见到这类标题时先看一眼里面的分类器是 SVM、决策树还是 MLP——是这个说明思路对路如果是某个接好的深度学习框架反而要警惕因为那更像调包教程而不像机器学习大作业。下一章开始做数据这是整个项目真正拉开差距的地方。3. 数据准备与预处理识别率 70% 还是 95%差别在这一步3.1 数据集从哪里来公开人脸库与自采数据的取舍数据集是“先有数据还是先有模型”这个问题里最现实的一环。课设常见三种来源压缩包自带数据、公开人脸库、自己用摄像头采集。如果压缩包里有现成的数据目录直接沿用即可但我会建议读懂它的目录组织方式——学号/姓名作为文件夹名、每个文件夹下是这个人不同姿态的照片这种结构本身就是一种标签体系。没有自带数据的话公开人脸库是最省事的选择。ORL 库 40 个人每人 10 张正面照规模小、光照相对统一非常适合做课程实验想增加挑战性可以换 Yale 库光照变化更大。注意用公开数据集时报告里要写清楚来源和引用这是学术规范问题答辩时老师很在意。自采数据更贴近门禁机场景但有三件事要提前约定好拍谁、拍多少、怎么拍。我一般建议至少 5 个人每人 30 到 50 张覆盖正面、左右偏转 15 度、戴不戴眼镜、室内灯光和背光各一组。数据采集前要让对方知情同意采集完按“姓名_序号.jpg”的规则命名不要创建“新建文件夹(3)”这种目录否则后面遍历代码要改半天。3.2 人脸检测与对齐把视频帧里的脸裁成统一尺寸拿到原始照片后第一步不是训练而是做检测和对齐。这里给出一段可用的预处理函数核心思路是先检测人脸框再裁剪缩放最后归一化像素值。import os import cv2 import numpy as np FACE_SIZE 112 # 统一缩放到 112x112兼顾细节与计算量 def load_cascade(): # OpenCV 自带的 frontalface 模型适合正脸居多的课设数据 cascade_path cv2.data.haarcascades haarcascade_frontalface_default.xml return cv2.CascadeClassifier(cascade_path) def preprocess_face(img, cascade, face_sizeFACE_SIZE): gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 直方图均衡化把过暗或过亮的像素分布拉开缓解光照差异 gray cv2.equalizeHist(gray) faces cascade.detectMultiScale( gray, scaleFactor1.1, # 每轮检测图像缩小 1.1 倍值越小越精细也越慢 minNeighbors5, # 候选框至少经过 5 重验证才保留越大误检越少 minSize(80, 80) # 小于 80x80 的区域直接忽略 ) if len(faces) 0: return None x, y, w, h faces[0] # 多人场景可循环处理单人课设先取最大框即可 roi gray[y:yh, x:xw] face_resized cv2.resize(roi, (face_size, face_size)) return face_resized / 255.0 # 归一化到 [0,1]让特征数值量纲一致这段代码有三个参数值得细说。scaleFactor1.1 表示每次检测将图像缩小 10%数值越小检测越耗时但有可能会找回偏小的脸minNeighbors5 控制误检与漏检的平衡设大了容易漏脸设小了背景区域会被当成脸minSize 直接过滤掉远处的路人脸在门禁和考勤场景非常实用。需要说明真正的“对齐”需要眼睛或鼻尖关键点。上面的退化方案只是把人脸框裁出来做缩放当人脸左右偏转较大时框的中心点并不等于面部中心。工程上更可靠的做法是先用关键点模型检出双眼坐标再用仿射变换把双眼映射到固定位置。课设里如果没有现成关键点模型至少保证裁剪框上方留一点额头区域别把脸裁切掉一角这已经是成本最低的有效对齐。3.3 划分训练集和验证集按人头分不按帧分预处理完的数据要划分训练集和验证集。这里有一个极常见却隐蔽的错误用随机划分把同一个人的照片同时分到训练集和验证集。人脸照片之间相似度极高模型实际上“见过”验证集里那个人的脸结果就是验证准确率虚高换个人就现原形。正确做法是按人头分组划分sklearn 里用 GroupShuffleSplit 可以一步到位。from sklearn.model_selection import GroupShuffleSplit # X 是所有预处理后的特征矩阵y 是姓名标签persons 是每个人唯一的组编号 gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, val_idx next(gss.split(X, y, groupspersons)) X_train, X_val X[train_idx], X[val_idx] y_train, y_val y[train_idx], y[val_idx]这段代码里的 groups 参数是防止数据泄漏的关键。persons 数组里同一个人的所有照片必须对应同一个 ID而不是按文件名随机编号。划分之后最好检查一遍验证集里是否包含与训练集相同的人检查方式很简单打印 train_idx 和 val_idx 对应的人名集合确认交集为空。这一步花不了两分钟能避免整个实验结论失去意义。数据集就绪后下一个问题是特征怎么提取。如果你用的是 HOG 这类手工特征特征维度取决于图像尺寸和分块参数如果走预训练模型特征维度可能是几百到一千多。两种方式写法差异很大下一章分开讲。4. 特征提取与分类器训练把一张脸变成一串可比较的数字4.1 手工特征路线HOG 特征配合 SVM机器学习课设里最正统的做法是手工特征加分类器。我倾向于用 HOG 而不是 LBPH因为 OpenCV 的 LBPH 直接封装好了匹配逻辑绕过了 sklearnHOG 特征则能正常走“提取特征 → 标准化 → 训练模型 → 调参”这个标准流程。HOG方向梯度直方图的核心思想是把图像划分成小格子统计每个格子里的梯度方向分布。对人脸来说眼睛、鼻梁、嘴角这些位置的梯度方向有相对稳定的模式正好可以作为身份特征。import numpy as np from skimage.feature import hog from sklearn.svm import SVC from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.model_selection import GridSearchCV def extract_hog(face_gray): # face_gray 是上一章预处理输出的 112x112 灰度图像素范围 [0,1] fd hog( face_gray, orientations9, # 梯度方向分成 9 个桶类似直方图的 bin 数 pixels_per_cell(8, 8), # 每个格子 8x8 像素 cells_per_block(2, 2), # 每个块包含 2x2 个格子用于局部归一化 block_normL2-Hys # L2 归一化变体对光照变化更稳 ) return fd # 假设 X_train 是 n 张 112x112 的灰度图数组 X_train_hog np.array([extract_hog(x) for x in X_train]) X_val_hog np.array([extract_hog(x) for x in X_val]) pipe make_pipeline( StandardScaler(), # 特征数值范围差异大先标准化再进 SVM SVC(kernelrbf, class_weightbalanced) ) param_grid { svc__C: [1, 10, 100], # 正则化系数越大对训练样本拟合越强 svc__gamma: [0.001, 0.0001], # RBF 核作用半径越小决策边界越平滑 } grid GridSearchCV(pipe, param_grid, cv3, scoringf1_macro, n_jobs-1) grid.fit(X_train_hog, y_train) print(best score:, grid.best_score_, params:, grid.best_params_) print(validation accuracy:, grid.score(X_val_hog, y_val))三个参数务必理解后再改。orientations9 表示每个格子统计 9 个方向的梯度强度太小丢失细节太大特征维度暴涨pixels_per_cell8 意味着 112x112 的图像会被切成 14x14 个格子C 是 SVM 正则化强度的倒数C1 是默认起点过度拟合时调小欠拟合时调大。gamma 在 RBF 核里控制单一样本的影响半径gamma 太大会把边界画得弯弯曲曲现实中表现为“只认训练集里那张脸”。这里有一个课设常见的误区为了追求准确率一上来就把 C 调到 1000。结果是小样本下训练集得分几乎满分验证集表现一般典型的过拟合。网格搜索的意义就在于让你看到这个趋势而不是挑一个最高验证分就完事。把不同 C 和 gamma 组合的验证分数画成热力图这张图放进报告里会非常加分。4.2 预训练模型抽特征在 sklearn 里接一个现成视觉编码器如果想让识别更鲁棒可以用预训练卷积网络提取特征再接 sklearn 分类器。这个方案的本质是用别人在海量图像上训练好的网络模型当特征提取器自己只训练最后那个分类器。这也是计算机视觉和机器学习结合得最舒服的方式——模型部分交代清楚来源分类器部分保留你的工作量。import torch from torchvision import models, transforms # MobilenetV2 轻量CPU 上跑得动新版本 torchvision 用 weights 参数 model models.mobilenet_v2(weightsmodels.MobileNet_V2_Weights.DEFAULT) model.eval() # 评估模式关闭 Dropout 和 BatchNorm 的随机性 # 输入尺寸 224x224均值方差使用 ImageNet 的统计值 transform transforms.Compose([ transforms.ToPILImage(), transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) # 去掉最后的全连接分类层只保留特征部分 feature_extractor torch.nn.Sequential(*list(model.children())[:-1]) def extract_cnn_feature(face_gray): face_float (face_gray * 255).astype(uint8) # 还原成 0-255 灰度图 face_rgb cv2.cvtColor(face_float, cv2.COLOR_GRAY2BGR) # 转三通道 x transform(face_rgb).unsqueeze(0) # 增加 batch 维度 with torch.no_grad(): feat feature_extractor(x) # 全局平均池化把 7x7x1280 压缩成一个 1280 维向量 feat feat.mean(dim(2, 3)).numpy() return feat.flatten()重点在最后一步全局平均池化。MobileNetV2 最后的特征图形状是 (batch, 1280, 7, 7)mean(dim(2,3)) 把空间维度全部平均掉得到不受输入尺寸影响的 1280 维向量这个向量就是“人脸 embedding”。首次运行这段代码会自动下载预训练权重耗时取决于网络状况之后的实验直接读缓存。14140 行说明feature_extractor 里的参数被冻结了反向传播不可能也不需要特征只算一次本地存成 npy 文件后后续调分类器参数不需要重新过网络。这一条能省下大量时间我看很多人每次调参都把整批图重新过一遍网络纯属浪费。拿到 embedding 后接的分类器我建议优先试 SVM 和 MLPClassifier。SVM 在几百维到一千多维的特征上表现稳定MLPClassifier 能让你在报告里多写一段神经网络。如果追求快随机森林也能用但效果通常会差一点。特征维度相对图像原始像素已经低了很多一般不需要再降维强行 PCA 反而可能丢掉区分信息。4.3 用摄像头跑通实时识别检测、识别、画框三步循环训练完成后实时演示是整个大作业最有视觉冲击力的环节也是答辩现场最容易出岔子的环节。一个稳定的实时识别循环就三段读帧、识别每个脸、画框和标签。import cv2 cap cv2.VideoCapture(0) cap.set(3, 640) # 画面宽度 640 cap.set(4, 480) # 画面高度 480 while True: ok, frame cap.read() if not ok: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray cv2.equalizeHist(gray) faces cascade.detectMultiScale(gray, scaleFactor1.1, minNeighbors5, minSize(80, 80)) for (x, y, w, h) in faces: roi cv2.resize(gray[y:yh, x:xw], (112, 112)) / 255.0 # 用训练好的模型预测注意 SVM 分类器是哪条路线就调用哪条 feat extract_hog(roi) if use_hog else extract_cnn_feature(roi) label grid.best_estimator_.predict([feat])[0] score grid.best_estimator_.decision_function([feat])[0] cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 2) cv2.putText(frame, f{label}: {score:.2f}, (x, y - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow(face-recognition, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()decision_function 返回的是样本到决策超平面的距离值越正代表越有把握。课设里把这个距离直接显示在框上方既有技术感又能直观看出分类器的确信程度。注意如果用的是 SVC 多分类decision_function 默认返回一对一投票的得分矩阵想省事可以在训练时设 decision_function_shapeovr。视频帧率方面预处理和特征提取都会耗时。如果发现画面明显卡顿常见做法是每隔一帧做一次识别但检测和画框可以每帧都做或者把输入尺寸降到 320x240帧率立刻上来。答辩演示时分辨率低一点但帧率流畅远比高分辨率卡成幻灯片体面。到这里一条“检测 → 对齐 → 特征 → 分类器 → 实时演示”的完整链路已经跑通。接下来聊聊我实际调这东西时踩过的坑有些坑几乎每个做人脸识别课设的人都会遇到。5. 人脸识别课设避坑记录五个让人崩溃的经典翻车现场5.1 训练集 98%摄像头下一换角度就认错现象训练集上验证准确率 98%网格搜索结果也很漂亮但拿到摄像头前稍微侧脸、仰头或者换个光线名字就开始在框上乱跳。原因数据集太“干净”了。采集或公开数据集里的人脸基本全是正脸、均匀光照预处理的直方图均衡化也压不住角度变化带来的特征偏移。模型实际上只学会了“正脸模板”没有学到“人脸”这个类别的不变特征。解决回到数据层面加变化。自采数据时把左右偏转、俯仰、戴不戴眼镜、室内灯光和靠窗背光都录进去如果只有公开数据集用随机亮度调整、水平翻转、轻微旋转变换来扩充。记住一个原则预处理里见过的变化模型才可能学会预处理剔除掉的变化等于主动放弃鲁棒性。5.2 特征矩阵维度对不上fit 直接报错现象训练时报 “setting an array element with a sequence” 或者维度不一致的 ValueError代码逻辑看着没问题但就是跑不过去。原因绝大多数情况是某张图没检测到人脸函数返回了 None 或者空数组但外层代码没有过滤直接把它 append 进特征列表最后列表里混入了不同长度的对象np.array 一转换就报错。解决在特征提取前统一过滤无效帧。检测不到脸的样本要么跳过并记录文件名要么先用最大的人脸框兜底。更稳妥的做法是在预处理函数里约定“返回 None 表示无效”外层代码遇到 None 就跳过最后再统计一下被跳过的数量。这个统计值写到课程报告里还能侧面证明数据清洗的严谨性。5.3 摄像头画面卡顿识别像放幻灯片现象实时演示时画面一卡一卡脸框和名字刷新跟不上人移动答辩现场非常尴尬。原因性能瓶颈通常不在检测模型而在你每帧都做了太重的事。比如把整帧缩放、每帧都跑一次完整特征提取、在 1920x1080 的分辨率上做检测。另一个隐蔽问题是 waitKey 参数设得过大比如 waitKey(30) 等于每帧强制等 30 毫秒帧率上限被锁死在 33。解决先量一下每一段的耗时用 time.time() 分别统计 detectMultiScale、特征提取、分类预测的开销。定位到瓶颈后把摄像头分辨率设成 640x480识别循环改成每 2 帧识别一次检测框依然每帧绘制。waitKey(1) 就够了不要用大数值去“控制帧率”界面卡顿大部分时候不是这个原因。5.4 同一人换眼镜或刘海后识别率骤降现象训练时这个人识别得好好的换一副眼镜、剪了刘海、或者戴个帽子直接认成另一个人。原因特征向量里把“这个人长什么样”和“这个人当时的装扮”混到了一起。HOG 特征本身对局部遮挡不敏感但裁剪对齐不严时刘海变化会显著改变整个特征向量的分布预训练 CNN 特征也有同样问题。解决核心还是数据增强和对齐。采集数据时跑完眼镜摘戴、刘海拨开、帽子摘戴几个变化模型才能区分“身份特征”和“装扮特征”。另外一个工程技巧是注册人脸库时不用单张图的特征而是用同一个人多张图的平均特征平均操作能抹掉一部分装扮带来的噪声。这个技巧放到第六章讲检索式识别时会显得特别自然。5.5 验证准确率虚高答辩演示时露馅现象本地代码里验证集准确率 99%答辩现场换了个环境识别率突然一半都不到整个人站在台上懵掉。原因十有八九是数据划分泄漏。常见三种泄漏一是随机划分训练集和验证集同一个人的照片两边都有二是先做全数据集上的预处理和特征提取再划分特征提取器“见过”验证集三是公开数据集里本身就包含重复或相近的帧没去重就划分。解决严格按人头分组划分保证验证集里的人名和训练集零交集特征提取必须在划分之后对训练集单独拟合标准化器也要用训练集的统计量。这里介绍一个最终验证方法把识别系统带到教室、实验室等一个完全没参与训练的数据环境里拍 5 个人的新照片看准确率还成不成立。这个“现场测试”的数据和结论写进报告含金量比验证集数字高得多。6. 把作业做成简历亮点注册式识别、指标与界面6.1 从分类器升级为人脸库检索注册式识别的思路分类器方案有个天然缺陷来一个新同学你得重新训练整个模型。真实门禁系统通常不是这么干的它们用注册式识别每个用户入库时只保存一条特征向量识别时拿当前人脸特征去库里找最相似的那条相似度超过阈值才算识别成功。注册式识别对课设来说并不复杂把上一章的训练环节替换成特征入库和比对即可。import numpy as np # db: {姓名: 特征向量}特征向量取多张图的平均值降低噪声 db {} for name in [zhang, li, wang]: embs [extract_cnn_feature(f) for f in load_faces_of_person(name)] db[name] np.mean(embs, axis0) def recognize(embedding, threshold0.65): best_name, best_sim None, -1 for name, emb in db.items(): sim np.dot(embedding, emb) / ( np.linalg.norm(embedding) * np.linalg.norm(emb)) if sim best_sim: best_name, best_sim name, sim return best_name if best_sim threshold else unknown这段代码里 threshold0.65 不能拍脑袋定。用验证集里每个人的多张照片两两比对算出“同一个人的相似度”和“不同人的相似度”两组分布取两个分布的交点附近作为阈值才是有说服力的做法。门禁机场景宁可不识别也不乱识别阈值适当调高这就是从“能用”到“好用”的差别。6.2 阈值怎么定用小批量验证样本找等错误率等人脸库建好后我有两个固定动作。第一把验证集的每一张照片去库里检索得到每个人所有样本的相似度然后分别画出正确匹配和错误匹配的相似度分布图。第二在这个分布图上找等错误率的点——也就是误识率和拒识率相等的阈值再根据具体场景往高或往低偏一点。门禁系统更怕陌生人闯入阈值就取得比等错误率更高实验室门禁场景更怕自己人被挡在外面阈值就略微放低。6.3 课程报告里写清这些指标答辩才能站稳报告里不要只放一个准确率。实际答辩中老师经常追问的指标有三个混淆矩阵能说清楚哪个人的容易被认错每个类别的精确率与召回率能反映人头不均衡时模型的偏向单次识别耗时常说明能否实时运行。如果做了注册式识别把阈值与错误率的关系曲线也画进去答辩时可讲的东西会多很多。如果还有余力给程序加一个简单的 Tkinter 界面把摄像头画面和识别结果放在同一个窗口里然后录一段演示视频放报告附录整个交付物的完整度立刻上了一个台阶。我个人的习惯是先把三人小库跑通再做十人规模扩展先固定数据采集规则再调模型参数每一步都记录在案。已经完成了最重要的部分——思路理顺了代码和数据只是时间问题。希望这套生产链路能帮你少走弯路交出一份真正能讲清楚的人脸识别大作业。本文还有配套的精品资源点击获取
返回列表