深度学习人流量检测毕设实战:从YOLO选型到系统落地
2026/9/24 18:43:17 网站建设 项目流程

简介:目标检测是计算机视觉的基础研究方向,其核心任务是定位并识别图像中的物体。在智慧城市、安防监控等场景中,人流量检测已成为目标检测技术的典型落地应用,用于客流统计、限流预警与轨迹分析。深度学习通过卷积神经网络自动学习特征表征,相比传统手工特征方案,在人群密集、光照变化等复杂条件下具有明显优势。YOLO作为单阶段检测器的代表,凭借速度快、精度高的特点,成为该类项目的主流选型。配合PyTorch工具链,可以高效完成从数据集构建、标注格式转换、模型训练到实时推理的完整流程。本文围绕人流量检测毕业设计中的真实工程实践,梳理YOLO选型要点、数据增强策略、视频流计数实现及常见避坑经验,为计算机视觉初学者提供一套可复用的落地路径。

1. 人流量检测毕设为什么都选深度学习:背景、边界与适用人群

人流量检测这个题目,在本科毕设里几乎算是常青树。早年大家用的是背景差分、HOG+SVM这类传统方案,一到商场入口、地铁闸机这种光线乱变、人挤人的场景就集体翻车,检测率掉得没法看。最近几年深度学习普及之后,这个题目换了一套做法:用卷积神经网络直接回归出画面里每个人的位置,配上Python和PyTorch这套工具链,一个人花三四周就能跑通一版能演示、能出数的系统——这就是“基于深度学习的人流量检测系统设计与实现”这个题目的真实定位。

这个项目解决的核心问题有两个:一是“画面里有多少人”,二是“人在哪”。前者用于客流统计、限流预警,后者用于热力分析和轨迹追踪。对毕设而言,绝大多数工作量落在“检测模型怎么选、数据怎么备、代码怎么组织、结果怎么展示”这四个环节上,而不是重新发明检测算法。适合这个题目的人群很明确:已经学过Python基础、上过机器学习或深度学习入门课,但还没有完整做过一个CV项目的本科生。项目难度天花板不高,但下限也不低——把YOLO跑通容易,把精度调上去、把演示系统做得完整,才是拿高分和答辩讲清楚的关键。

这篇笔记按我自己的落地习惯来写:先讲方案怎么选型,再给一套可以直接抄的数据准备与训练流程,然后落到Web端实时计数的实现,最后专门写一版避坑清单和答辩加分技巧。全程基于真实跑通过的做法,不是把论文目录抄一遍。

2. 人流量检测方案选型:为什么是YOLO系列而不是Faster R-CNN或SSD

2.1 主流检测模型在人流量场景下的对比和选型理由

人流量检测本质上是一个密集场景的目标检测任务。密集这两个字是选型的核心约束:人挨着人、人挡着人,小目标多,尺度差异大。拿这个约束去筛模型,Faster R-CNN这类两阶段检测器精度确实高,但推理速度在普通笔记本上只有几FPS,做实时视频流分析基本不用想;SSD速度不错,但对小目标的召回率偏低,而人流量场景里恰好大量目标是远端的、像素很小的行人,SSD在这种场景下的表现可以说是灾难性的。

YOLO系列是目前做这个题目最稳妥的选择,从YOLOv5到YOLOv8,社区生态成熟,预训练权重好找,标注工具和转换脚本齐全,而且新版本的训练脚本封装得很好,不要求你手写损失函数和NMS。我自己的经验是,毕设阶段选YOLOv5s或YOLOv8s即可,模型体积小、推理速度快,CPU上勉强能跑,GPU上非常流畅。为什么选s而不是m或l?因为人流量检测的数据量通常不大,你不太可能自己标几万张图,用大模型容易过拟合,而且答辩演示时笔记本的显卡带不动大模型,帧率掉到个位数会很尴尬。

性能指标方面,人流量检测关注两个指标:mAP(mean Average Precision)和FPS。mAP衡量检测准确度,FPS衡量实时性。对毕设来说,mAP在0.75以上、FPS在25以上,属于一个“拿得出手”的水平。要注意的是,mAP是整体指标,人流量场景建议额外关注小目标类别的AP值,这个值低了说明远端人没检测到,计数就会明显偏少。

2.2 单人检测还是人群计数回归:两种技术路线的边界

做毕设之前,必须先分清楚“人流量检测”和“人群计数”是两个不同的技术方向。人流量检测输出的是每个人的边界框(Bounding Box),可以进一步做跟踪、方向判断;人群计数输出的是密度图或一个总人数,适合远距离大范围场景,但拿不到个体位置。很多半路出家的教程把这两个混在一起,导致学生代码写得四不像。

毕设题目明确写了“检测系统”,所以走目标检测路线,输出边界框,这是对的。但需要知道另一个路线的存在,答辩时老师很可能问“为什么不用CSRNet这种密度回归方法”,你要能答上来:一是密度图方法适合极密集场景,二是在监控视角下无法给出个体位置,无法支撑“人流量”里“流量”这个动作层面的统计需求。流量意味着需要知道人从哪边进来、往哪边出去,这是检测框后面接跟踪逻辑才能回答的,密度图给不了。

2.3 环境搭建清单:Python版本、PyTorch、CUDA与OpenCV的配套关系

环境配置是这个项目的第一道坎,也是踩坑率最高的环节。我给出一套自用稳定的组合,照这个配能省很多事。

需要准备的环境组件包括:Python 3.8或3.10、PyTorch 2.x、CUDA 11.8或12.1、OpenCV-Python、Ultralytics YOLO包。这里有一个常见误区:很多人装了最新版Python 3.12,结果PyTorch的预编译包不支持,或者装了CUDA 12.4但显卡驱动版本太老,导致torch.cuda.is_available()返回False。我的建议是,毕设机器上如果显卡是NVIDIA 20系或30系,直接装CUDA 11.8 + PyTorch 2.0.1,这个组合被验证的次数最多,网上报错案例也最少。

如果你是用CPU跑,不需要装CUDA,但要做好推理速度慢的心理准备。CPU跑YOLOv8s,720P视频大概5-10FPS,演示静态图片没问题,做实时视频会卡顿。我一般建议学生至少找一张GTX 1060以上显卡的机器来做训练和演示,实在没有就用Colab,训练速度比本地CPU快几十倍。

3. 把公开数据集变成能训练的样本:标注格式转换与数据增强实操

3.1 人流量数据集选哪些公开资源,以及它们各自的坑

模型训练离不开数据。人流量检测可用的公开数据集大致有这么几类:通用检测数据集里挑person类,比如COCO数据集、VOC数据集;专门的行人检测数据集,比如Caltech Pedestrian、EuroCity Persons;以及人群计数数据集,比如ShanghaiTech,但这类是密度图格式,不能直接用于检测。毕设一般不建议自己从零标注,工程量太大,一张密集场景图标完要半小时,标200张就崩溃了。

我常用的做法是:用COCO数据集里的person类作为预训练来源,再用Mall Dataset或自己从监控视频抽帧标注200-500张作为微调数据。COCO自带标注文件是JSON格式,Mall Dataset提供的是MAT文件,而YOLOv8训练需要的是TXT格式,每行一个目标:类别id 归一化中心x 归一化中心y 归一化宽 归一化高。所以第一件事就是写一个格式转换脚本。

3.2 用Python写COCO和VOC标注转YOLO格式的转换脚本

下面这个脚本处理COCO JSON转YOLO TXT,是我自己项目里在用的,你直接改路径就能跑。它解决一个核心问题:COCO的边界框格式是[x, y, width, height],原点是左上角,要转成YOLO需要的[cx, cy, w, h]归一化格式,不转换的话训练出来的模型整个是乱的。

import json import os def coco_to_yolo(coco_json_path, output_dir): with open(coco_json_path, 'r', encoding='utf-8') as f: coco = json.load(f) # 建立图片id到文件名的映射 img_id_to_name = {} for img in coco['images']: img_id_to_name[img['id']] = img['file_name'] # 只保留person类别,COCO中person的id是1 category_ids = {cat['id']: cat['name'] for cat in coco['categories']} # 按图片维度聚合annotation annotations_by_img = {} for ann in coco['annotations']: if category_ids[ann['category_id']] != 'person': continue img_id = ann['image_id'] if img_id not in annotations_by_img: annotations_by_img[img_id] = [] annotations_by_img[img_id].append(ann) # 写入YOLO格式 for img_id, anns in annotations_by_img.items(): img_info = None for img in coco['images']: if img['id'] == img_id: img_info = img break w, h = img_info['width'], img_info['height'] txt_path = os.path.join(output_dir, os.path.splitext(img_id_to_name[img_id])[0] + '.txt') with open(txt_path, 'w') as f: for ann in anns: bbox = ann['bbox'] # [x, y, w, h] cx = (bbox[0] + bbox[2] / 2) / w cy = (bbox[1] + bbox[3] / 2) / h bw = bbox[2] / w bh = bbox[3] / h f.write(f"0 {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\n")

这段脚本的逻辑分三步:先解析COCO的JSON结构,拿到图片尺寸和标注框,然后筛选出person类别,最后把边界框从“左上角坐标+宽高”转成“中心点+宽高”并做归一化。其中有个代码细节要解释:cx = (bbox[0] + bbox[2] / 2) / w,这个减法不需要,COCO的x和y本来就是左上角坐标,中心点直接用x + w/2,不需要额外处理,某些教程里写成(x + w/2) / w即可。

参数说明:--coco_json_path传入COCO的annotations文件路径,output_dir是存放TXT标注的目录,建议和图片文件保持同样的文件名前缀。另外这个脚本只保留了person类,如果后续想增加“人群密度等级”这个分类,可以修改category筛选逻辑,把bag、chair之类干扰项过滤掉。

VOC格式的XML转YOLO也常遇到,写一个简版:

import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_dir, output_dir): for xml_file in os.listdir(xml_dir): if not xml_file.endswith('.xml'): continue tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() w = int(root.find('size/width').text) h = int(root.find('size/height').text) txt_file = os.path.join(output_dir, xml_file.replace('.xml', '.txt')) with open(txt_file, 'w') as f: for obj in root.findall('object'): name = obj.find('name').text if name != 'person': continue bndbox = obj.find('bndbox') xmin = int(bndbox.find('xmin').text) ymin = int(bndbox.find('ymin').text) xmax = int(bndbox.find('xmax').text) ymax = int(bndbox.find('ymax').text) cx = (xmin + xmax) / 2 / w cy = (ymin + ymax) / 2 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h f.write(f"0 {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\n")

这段就不再解释了,逻辑完全一致,区别只在解析的XML标签结构不一样。

3.3 数据增强的必调参数:Mosaic、翻转、亮度扰动如何设置

数据增强直接决定模型泛化能力。YOLOv8的默认增强参数在大多数场景下是合理的,但人流量检测有一项需要特别调整:亮度扰动。监控场景的光线变化剧烈,早晚反差大,如果增强参数里亮度扰动范围太小,模型到了阴影区域就容易漏检。我习惯把hsv_v(饱和度扰动)从默认的0.4调到0.5,把hsv_h(色调扰动)从0.015调到0.02,适当增加对光照变化的鲁棒性。

Mosaic增强默认是开启的,这个要保持打开。Mosaic把四张图拼成一张训练,能显著提升小目标的检测能力,正好应对远端行人占比高的情况。如果你的显存不够(8G以下),把mosaic设为0.5而不是关闭,否则小目标的召回率会掉一个档次。还有一个参数是fliplr,水平翻转,默认0.5,这个对人流量场景是安全的,因为行人不分左右,但要注意翻转后标注框的坐标在YOLO内部会同步调整,不需要你手动改标注文件。

4. 基于YOLOv8的人流量检测核心实现:训练、推理到视频流计数

4.1 训练配置:数据集目录结构、YAML文件与关键超参数

数据准备好之后,按YOLOv8要求的目录结构组织文件。数据集根目录下分images/trainimages/vallabels/trainlabels/val四个子目录,图片和TXT标注文件保持同名。注意TXT文件里如果某张图没有任何目标,对应的TXT文件是空文件,不要删除,YOLO训练逻辑依赖这个文件判断“背景图”。

训练前需要写一个数据集YAML文件,内容如下:

path: ./datasets/person_flow train: images/train val: images/val names: 0: person

这里path是数据集根目录的相对路径,trainval是相对于path的图片目录路径,names是类别名映射。整个YAML文件就这三块,看起来简单但容易出错的地方是路径层级:train字段写的是图片所在目录,不要直接写成图片文件列表的绝对路径。另外类别ID从0开始,必须和标注TXT文件里的类别数字保持一致。

核心训练命令和参数说明如下:

yolo detect train data=person_flow.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 lr0=0.01 device=0

拆解一下关键参数:model=yolov8s.pt表示加载COCO预训练权重作为初始权重,这比从头训练能节省大量时间和数据;epochs=100在毕设数据规模(几百张)下足够,再多就会过拟合;imgsz=640是训练分辨率,这个值不要调成1280,虽然精度会提升,但显存占用翻倍,而且推理时你不可能都用1280跑,会造成训练和推理分辨率不一致的精度损失;device=0表示用第一块GPU训练,CPU训练就把这个参数删掉。

训练过程中要盯两个指标:train/cls_lossval/mAP50-95。如果val/mAP50-95在40个epoch后还在缓慢上升,说明数据集质量可以;如果loss下降但mAP纹丝不动,大概率标注格式有问题,回去检查TXT文件。

4.2 模型推理封装:加载权重、处理单帧图片并输出计数结果

训练完成后,模型推理这部分要封装成一个类,方便后面Web端调用。下面是一个完整的推理模块,包含加载模型、清洗检测结果、统计人数三个功能:

import cv2 from ultralytics import YOLO class PersonDetector: def __init__(self, weights_path, conf_thres=0.35, iou_thres=0.45): self.model = YOLO(weights_path) self.conf_thres = conf_thres self.iou_thres = iou_thres def detect_frame(self, frame): results = self.model.predict( frame, conf=self.conf_thres, iou=self.iou_thres, verbose=False )[0] boxes = results.boxes if boxes is None or len(boxes) == 0: return [], 0 # 提取边界框坐标和置信度 xyxy = boxes.xyxy.cpu().numpy() confs = boxes.conf.cpu().numpy() person_list = [] for box, conf in zip(xyxy, confs): x1, y1, x2, y2 = [int(v) for v in box] person_list.append({ 'bbox': [x1, y1, x2, y2], 'confidence': round(float(conf), 4) }) return person_list, len(person_list) def detect_video(self, video_path, show=False): cap = cv2.VideoCapture(video_path) total_count = 0 frame_count = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break _, count = self.detect_frame(frame) total_count += count frame_count += 1 cap.release() # 返回平均每帧人数和总帧数 return total_count / max(frame_count, 1), frame_count

这个类的设计有三个关键点。第一,conf_thres默认0.35,对人流量场景是合适的,调低到0.2会捡回很多漏检的小目标,但也会混入大量误检的背景框,需要你自己权衡;第二,verbose=False很关键,不然推理时控制台会被YOLO的日志刷屏,影响Web服务排错;第三,上面视频统计的逻辑是“每帧人数累加再平均”,这只适合静态演示,真实流量统计必须基于跟踪去重,否则同一个人连续出现在30帧里会被计30次,这个在后面章节单独展开。

4.3 用推理结果绘制可视化:画框、密度标签和热度图叠加

模型输出只是坐标和置信度,要让它变成“能演示的东西”,需要把检测结果画回原图。这里用OpenCV实现,核心是cv2.rectangle画检测框和cv2.putText写人数标签:

def draw_detections(frame, person_list, total_count, density_level): for person in person_list: x1, y1, x2, y2 = person['bbox'] conf = person['confidence'] # 画绿色边界框 cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) # 在框上方写置信度 label = f'{conf:.2f}' cv2.putText(frame, label, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) # 左上角显示总人数和密度等级 cv2.putText(frame, f'Count: {total_count}', (15, 35), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 3) cv2.putText(frame, f'Density: {density_level}', (15, 75), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 0, 0), 2) return frame

这里density_level可以按人数映射成“畅通/拥挤/拥堵”三个等级,比如:人数0-10为畅通,11-30为拥挤,30以上为拥堵。这个可视化在答辩PPT里的演示效果比干巴巴的准确率数字好得多,很多老师就吃这一套。调试时有个经验:框的粗细和字体大小要在实际播放分辨率下看效果,在1080P源视频上调好的参数,缩放到窗口里显示会变细变小。

5. 人流量检测避坑指南:从环境配置到计数逻辑的5个翻车现场

5.1 环境配置翻车:CUDA装好了但PyTorch检测不到GPU

现象:torch.cuda.is_available()返回False,训练时被迫用CPU,速度慢到怀疑人生。

原因:绝大多数情况是PyTorch版本和CUDA版本不对应。PyTorch 2.0.1默认对应CUDA 11.7或11.8,如果你装的是CUDA 12.4,GPU驱动版本不够就会静默回退到CPU模式。

解决:先跑nvidia-smi查看驱动支持的最高CUDA版本,然后去PyTorch官网用对应版本的安装命令重装。我踩过最深的坑是装了最新的PyTorch 2.3导致cuDNN不兼容,后来固化用“PyTorch 2.0.1 + CUDA 11.8”这个组合再也没出错。

5.2 数据标注翻车:归一化坐标写成了像素坐标

现象:训练时loss不降,或者画出来的检测框偏移到图片角落。

原因:YOLO格式要求边界框坐标必须是归一化的,也就是0到1之间的小数。如果你从LabelImg导出时选了Pascal VOC格式再手动转,很容易把像素坐标直接写进TXT,模型训练时会把上千像素的坐标当成0-1的小数,整个边界框计算全乱。

解决:写一个验证脚本,随机挑一张图把TXT里的坐标还原成像素画回去,和原图比对。这个脚本很简单但极其有效,30秒就能发现问题。

5.3 标签类别翻车:背景被标注成了目标

现象:mAP不错但实际使用大量误报,把椅子、海报上的人像都检测出来了。

原因:标注时没有把遮挡严重的半身人、模糊的远景人过滤掉,或者把画面中印着人的广告牌也标成了person。模型学到了“凡是人形轮廓”就是目标的错误特征。

解决:只标注画面中可辨认的真人,遮挡超过70%的不标。宁可少标不可错标,错标带来的误检在答辩现场被老师指出来会非常尴尬。

5.4 计数逻辑翻车:同一个人被重复统计导致数字虚高

现象:静态图片计数正常,一旦跑视频流,人数统计值像滚雪球一样涨。

原因:这是所有做过人流量检测的人都会遇到的一个问题。视频是连续的帧序列,同一个行人在30帧里被检测到30次,如果用每帧人数累加那就是严重的重复计数。

解决:常见做法是引入IoU跟踪。检测到上一帧的某个框和当前帧某个框的IoU大于0.5,就认为是同一个人,只更新位置不增加计数。更轻量的做法是加一个“冷却期”,举个例子:维护一个人物ID列表,每个ID有最近一次出现的帧号,如果当前帧检测框与某个ID的历史位置重叠且帧间隔小于10帧,则复用ID,否则新建ID并累计人数。这个逻辑在毕设里属于加分项,因为说明你考虑了实际场景的复杂度。

5.5 Web服务翻车:用Flask跑模型导致接口卡死

现象:Flask服务启动正常,浏览器访问时画面卡住不动。

原因:YOLO模型预测是同步阻塞操作,一个请求进来模型还没算完,下一个请求就来了,Flask默认单线程直接卡死。

解决:用Flask的threaded=True开启多线程模式,或者用生成器实现视频流的异步推送。第二个做法是主流方案:通过Response(generate(), mimetype='multipart/x-mixed-replace; boundary=frame')把检测后的帧实时推送到浏览器,这是监控系统最通用的流媒体实现方式。

6. 进阶优化与答辩加分:跟踪算法、模型轻量化与实验报告写法

6.1 引入ByteTrack做多目标跟踪

如果想让系统真正达到“流量统计”的水平,只做检测是不够的,必须绑定跟踪。ByteTrack是目前工程上最好用的轻量级跟踪算法,与YOLOv8无缝配合,在CPU上也能跑。核心逻辑是:先用检测器得到每一帧的边界框,然后通过卡尔曼滤波预测下一帧每个轨迹的位置,再用匈牙利算法做检测框和预测框的匹配。用它替换掉自己写的IoU冷却期逻辑后,计数准确率能提升10个百分点以上,而且能输出每个人的运动轨迹,这一块放在论文里是“系统创新点”。

实现方式如下:

from collections import defaultdict import numpy as np class FlowCounter: def __init__(self, max_lost=10): self.tracks = {} self.next_id = 0 self.max_lost = max_lost self.total_in = 0 self.total_out = 0 def update(self, detections, line_y): # detections: list of [x1, y1, x2, y2] # line_y: 虚拟计数线的y坐标 current_ids = [] for det in detections: x1, y1, x2, y2 = det center_y = (y1 + y2) / 2 matched = False for track_id, track in self.tracks.items(): # 简单的中心点距离匹配 if abs(track['last_center_y'] - center_y) < 50: # 判断是否跨越计数线 prev_y = track['last_center_y'] if prev_y < line_y and center_y >= line_y: self.total_in += 1 elif prev_y > line_y and center_y <= line_y: self.total_out += 1 track['last_center_y'] = center_y track['lost'] = 0 current_ids.append(track_id) matched = True break if not matched: self.tracks[self.next_id] = { 'last_center_y': center_y, 'lost': 0 } current_ids.append(self.next_id) self.next_id += 1 # 清理丢失的目标 for track_id in list(self.tracks.keys()): if track_id not in current_ids: self.tracks[track_id]['lost'] += 1 if self.tracks[track_id]['lost'] > self.max_lost: del self.tracks[track_id] return self.total_in, self.total_out

这段代码的思路是:维护每个目标的中心点历史位置,检测框匹配到同一目标后,比较前后帧中心点的y坐标和虚拟计数线line_y的关系,从线上方跨到下方计为进入,反向计为出去。代码里的匹配阈值50像素在720P视频里够用,换成1080P需要相应放大。

6.2 模型轻量化:TensorRT加速和模型蒸馏的取舍

如果你的答辩机器是Windows笔记本,TensorRT大概率用不上,因为TensorRT在Windows上的支持比较别扭。更实际的做法是:训练时用YOLOv8s,推理时导出成ONNX格式,再用ONNX Runtime做推理,速度比PyTorch原生推理快30%左右。导出命令很简单:

yolo export model=best.pt format=onnx imgsz=640

导出后推理代码只需改一行:model = YOLO('best.onnx')。这个优化在答辩现场演示实时视频流时非常重要,帧率从15FPS提到25FPS是肉眼可见的差距。

模型蒸馏在毕设阶段不建议碰,原因一是时间成本高,二是你需要同时训练大模型和小模型,显存不够容易翻车。把精力放在数据质量、跟踪逻辑和可视化这三个更容易出成果的地方,性价比高得多。

6.3 实验部分的写作技巧和答辩应答策略

毕业论文的实验部分,最忌讳只写一个总mAP。要把实验拆成几个维度来写,形成完整的对比表。我的建议是至少包含三组对比实验:不同模型(YOLOv5s对比YOLOv8s)、不同置信度阈值(0.25对比0.35对比0.5)、不同输入分辨率(640对比960)。每组实验记录mAP、FPS、每帧检测人数三个指标,形成表格。这样的实验设计能说明你有做工程优化的思考,不是跑通代码就完事。

答辩时老师最喜欢问的问题是怎么保证系统的可靠性,也就是误检和漏检怎么控制。你可以回答:误检靠置信度阈值和NMS阈值联动控制,漏检靠数据增强和Mosaic策略提升小目标召回率,并以自己的实验结果为例说明。这一套话术下来,比单纯念论文摘要效果好得多。

做这个项目最大的教训是:早点确定模型基线,不要在环境配置和选型上反复横跳。我见过太多学生今天想试YOLOv5明天想试EfficientDet,最后代码没跑通就快答辩了。选型定下来就一口气把流程走通,哪怕性能不是最优,先有一个完整可演示的版本,再逐步优化。这比追求完美方案但卡在第一步强太多。希望这些经验能帮你在毕设路上少熬夜、少踩坑。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询