项目摘要
本项目旨在开发一个基于深度学习与Web技术的前后端分离式花生种子霉变智能识别与检测系统。系统核心采用先进的YOLOv8/v10/v11/v12系列目标检测模型,对花生种子图像进行高效、精准的二分分类(‘with mold’ 霉变 / ‘without mold’ 正常)。后端使用SpringBoot框架构建RESTful API,前端提供友好的Web交互界面,实现了用户管理、多模态检测(图像、视频、实时摄像头)、AI分析结果可视化与数据管理等功能。创新性地集成DeepSeek智能分析以增强检测能力,并将所有识别记录与用户信息持久化至MySQL数据库。该系统为农业品质控制提供了一套自动化、可视化的解决方案,有效提升了花生霉变检测的效率和准确性。
引言
在农业生产与食品加工领域,花生作为一种重要的经济作物和油料作物,其储存期间的霉变问题不仅会造成巨大的经济损失,更会因产生的黄曲霉素等有害物质严重威胁人畜健康。传统的人工目视检测方法效率低下、主观性强且易疲劳,难以满足大规模、高标准的质检需求。近年来,深度学习技术,特别是以YOLO系列为代表的目标检测算法,在图像识别领域取得了突破性进展,为自动化视觉检测提供了新的技术路径。
为此,我们设计并实现了这个基于YOLOv8/v10/v11/v12与SpringBoot的前后端分离花生种子霉变识别检测系统。本系统旨在通过集成最先进的深度学习模型与现代化的Web开发框架,构建一个功能完备、操作便捷、响应迅速的在线检测平台。它不仅支持多种检测模式和模型的灵活切换,还通过数据可视化和智能记录管理,为用户提供了从检测到分析的完整工作流,有望在农产品质量安全监测中发挥重要作用。
项目背景与介绍
1. 背景介绍
花生霉变是贯穿于其采收、储存、运输和加工各个环节的全球性难题。霉变花生是黄曲霉素的主要来源,而黄曲霉素是世界卫生组织划定的一类致癌物,具有强烈的毒性和致癌性。因此,对花生进行快速、准确的霉变检测,是实现农产品安全溯源、保障消费者健康、减少产后损失的关键环节。
随着计算机视觉和人工智能技术的飞速发展,基于深度学习的智能检测方法正逐步取代传统方法。其中,YOLO模型因其卓越的实时性和高精度而备受青睐。从YOLOv8到不断涌现的v10、v11、v12等版本,其性能持续优化,为复杂场景下的细粒度目标检测提供了强有力的技术支撑。同时,SpringBoot作为Java领域最流行的企业级开发框架,其简化配置、快速开发的特点非常适合构建稳健的后端服务。前后端分离的架构模式则确保了系统的可扩展性、可维护性以及良好的用户体验。
2. 系统介绍
本项目是一个集成了最新YOLO系列模型与SpringBoot框架的全栈Web应用,核心目标是实现花生种子霉变的智能化识别与检测。
系统核心特性如下:
先进的检测模型:系统支持在YOLOv8, v10, v11, v12四种模型间动态切换,允许用户根据对精度和速度的不同需求选择最合适的模型,数据集针对“有霉变”和“无霉变”两类进行训练。
多模态检测功能:全面支持图像上传检测、视频文件分析以及摄像头实时流检测,覆盖了主要的应用场景。
强大的Web交互平台:采用前后端分离架构,后端SpringBoot提供稳定的API服务,前端界面提供直观的操作体验。功能包括:
用户体系:完整的登录/注册(含密码安全检测)、个人中心(信息修改)、以及管理员对用户的管理模块。
数据管理:所有检测结果(图片、视频、摄像头)均保存至MySQL数据库,并提供独立的记录管理页面进行查看、追溯和管理。
信息可视化:通过图表等形式对检测数据进行可视化展示,便于用户进行统计分析。
增强的AI分析:集成DeepSeek智能分析功能,为图像检测提供更深入的洞察。
个性化设置:支持更换导航栏背景颜色等界面自定义选项,提升用户体验。
项目源码+数据集下载链接
完整代码在哔哩哔哩视频下方简介内获取:
基于YOLOv8/v10/v11/v12与SpringBoot的前后端分离花生种子霉变识别检测系统(DeepSeek智能分析+web交互界面)_哔哩哔哩_bilibili
基于YOLOv8/v10/v11/v12与SpringBoot的前后端分离花生种子霉变识别检测系统(DeepSeek智能分析+web交互界面)_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1gACtBTEHe/?spm_id_from=333.1387.homepage.video_card.click&vd_source=549d0b4e2b8999929a61a037fcce3b0f
https://www.bilibili.com/video/BV1gACtBTEHe
目录
项目摘要
引言
项目背景与介绍
1. 背景介绍
2. 系统介绍
项目源码+数据集下载链接
功能模块
登录注册模块
可视化模块
更换导航栏背景颜色
图像检测模块
视频检测模块
实时检测模块
图片识别记录管理
视频识别记录管理
摄像头识别记录管理
用户管理模块
数据管理模块(MySQL表设计)
模型训练结果
YOLOv8
YOLOv10
YOLOv11
YOLOv12
前端代码展示
后端代码展示
项目源码+数据集下载链接
项目安装教程
功能模块
✅ 用户登录注册:支持密码检测,保存到MySQL数据库。
✅ 支持四种YOLO模型切换,YOLOv8、YOLOv10、YOLOv11、YOLOv12。
✅ 信息可视化,数据可视化。
✅ 图片检测支持AI分析功能,deepseek
✅ 支持图像检测、视频检测和摄像头实时检测,检测结果保存到MySQL数据库。
✅ 图片识别记录管理、视频识别记录管理和摄像头识别记录管理。
✅ 用户管理模块,管理员可以对用户进行增删改查。
✅ 个人中心,可以修改自己的信息,密码姓名头像等等。
✅ 支持更换导航栏背景颜色
登录注册模块
可视化模块
更换导航栏背景颜色
图像检测模块
YOLO模型集成(v8/v10/v11/v12)
DeepSeek多模态分析
支持格式:JPG/PNG/MP4/RTSP
视频检测模块
实时检测模块
图片识别记录管理
视频识别记录管理
摄像头识别记录管理
用户管理模块
数据管理模块(MySQL表设计)
users- 用户信息表
imgrecords- 图片检测记录表
videorecords- 视频检测记录表
camerarecords- 摄像头检测记录表
模型训练结果
#coding:utf-8 #根据实际情况更换模型 # yolon.yaml (nano):轻量化模型,适合嵌入式设备,速度快但精度略低。 # yolos.yaml (small):小模型,适合实时任务。 # yolom.yaml (medium):中等大小模型,兼顾速度和精度。 # yolob.yaml (base):基本版模型,适合大部分应用场景。 # yolol.yaml (large):大型模型,适合对精度要求高的任务。 from ultralytics import YOLO model_path = 'pt/yolo12s.pt' data_path = 'data.yaml' if __name__ == '__main__': model = YOLO(model_path) results = model.train(data=data_path, epochs=500, batch=64, device='0', workers=0, project='runs', name='exp', )YOLOv8
YOLOv10
YOLOv11
YOLOv12
前端代码展示
部分代码
<template> <div class="recognition-container"> <div class="recognition-content"> <!-- 搜索区域 --> <div class="search-section"> <el-card shadow="hover" class="search-card"> <div class="search-header"> <h3>识别记录筛选</h3> <el-button type="primary" @click="getTableData()" class="refresh-btn"> <el-icon><ele-Refresh /></el-icon> 刷新数据 </el-button> </div> <div class="search-controls"> <div class="search-inputs"> <el-input v-model="state.tableData.param.search1" size="default" placeholder="请输入识别时间" class="search-field" clearable> <template #prefix> <el-icon><ele-Calendar /></el-icon> </template> </el-input> <el-input v-model="state.tableData.param.search2" size="default" placeholder="请输入识别结果" class="search-field" clearable> <template #prefix> <el-icon><ele-Search /></el-icon> </template> </el-input> </div> <el-button size="default" type="primary" class="search-btn" @click="getTableData()"> <el-icon><ele-Filter /></el-icon> 筛选结果 </el-button> </div> </el-card> </div> <!-- 数据表格区域 --> <div class="data-section"> <el-card shadow="hover" class="table-card"> <template #header> <div class="table-header"> <span>识别记录列表</span> <div class="table-stats"> <el-tag type="info">共 {{ state.tableData.total }} 条记录</el-tag> </div> </div> </template> <el-table :data="state.tableData.data" style="width: 100%" class="recognition-table" v-loading="state.tableData.loading"> <el-table-column type="expand"> <template #default="props"> <div class="detail-panel"> <h4 class="detail-title">详细识别结果</h4> <el-table :data="props.row.family" class="nested-table" empty-text="暂无详细数据"> <el-table-column prop="label" label="识别结果" align="center" /> <el-table-column prop="confidence" label="置信度" show-overflow-tooltip align="center"> <template #default="scope"> <el-progress :text-inside="true" :stroke-width="20" :percentage="Math.round(scope.row.confidence * 100)" :status="getConfidenceStatus(scope.row.confidence)" style="width: 120px; margin: 0 auto;" /> </template> </el-table-column> <el-table-column prop="startTime" label="识别时间" align="center" /> </el-table> </div> </template> </el-table-column> <el-table-column prop="num" label="序号" width="80" align="center" /> <el-table-column prop="inputImg" label="原始图片" width="120" align="center"> <template #default="scope"> <div class="img-wrapper" @click="previewImage(scope.row.inputImg, '原始图片')"> <img :src="scope.row.inputImg" alt="原始图片" /> <div class="img-overlay"> <el-icon><ele-ZoomIn /></el-icon> </div> </div> </template> </el-table-column> <el-table-column prop="outImg" label="预测图片" width="120" align="center"> <template #default="scope"> <div class="img-wrapper" @click="previewImage(scope.row.outImg, '预测图片')"> <img :src="scope.row.outImg" alt="预测图片" /> <div class="img-overlay"> <el-icon><ele-ZoomIn /></el-icon> </div> </div> </template> </el-table-column> <el-table-column prop="weight" label="识别权重" show-overflow-tooltip align="center"> <template #default="scope"> <el-tag v-if="scope.row.weight" type="success">{{ scope.row.weight }}</el-tag> <span v-else class="empty-text">-</span> </template> </el-table-column> <el-table-column prop="conf" label="最小阈值" show-overflow-tooltip align="center"> <template #default="scope"> <el-tag v-if="scope.row.conf" type="warning">{{ scope.row.conf }}</el-tag> <span v-else class="empty-text">-</span> </template> </el-table-column> <el-table-column prop="ai" label="AI助手" show-overflow-tooltip align="center"> <template #default="scope"> <el-tag v-if="scope.row.ai" type="info">{{ scope.row.ai }}</el-tag> <span v-else class="empty-text">-</span> </template> </el-table-column> <el-table-column prop="suggestion" label="AI建议" show-overflow-tooltip align="center"> <template #default="scope"> <div class="suggestion-cell"> <span v-if="scope.row.suggestion" :title="scope.row.suggestion"> {{ scope.row.suggestion }} </span> <span v-else class="empty-text">-</span> </div> </template> </el-table-column> <el-table-column prop="startTime" label="识别时间" width="180" align="center" /> <el-table-column prop="username" label="识别用户" show-overflow-tooltip align="center"> <template #default="scope"> <el-tag v-if="scope.row.username" type="primary">{{ scope.row.username }}</el-tag> <span v-else class="empty-text">-</span> </template> </el-table-column> <el-table-column label="操作" width="180" fixed="right" align="center"> <template #default="scope"> <div class="action-buttons"> <el-button size="small" type="primary" @click="onViewDetail(scope.row)" class="view-btn"> <el-icon><ele-View /></el-icon> 详情 </el-button> <el-button size="small" type="danger" @click="onRowDel(scope.row)" class="delete-btn"> <el-icon><ele-Delete /></el-icon> 删除 </el-button> </div> </template> </el-table-column> </el-table> <!-- 分页 --> <el-pagination @size-change="onHandleSizeChange" @current-change="onHandleCurrentChange" class="pagination-bar" :pager-count="5" :page-sizes="[10, 20, 30]" v-model:current-page="state.tableData.param.pageNum" background v-model:page-size="state.tableData.param.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="state.tableData.total"> </el-pagination> </el-card> </div> </div> <!-- 详情弹窗 --> <el-dialog v-model="state.detailDialog.visible" :title="state.detailDialog.title" width="800px" align-center class="detail-dialog"> <div class="detail-content" v-if="state.detailDialog.data"> <div class="detail-section"> <h3 class="section-title">图片信息</h3> <div class="image-comparison"> <div class="image-item"> <p class="image-label">原始图片</p> <img :src="state.detailDialog.data.inputImg" alt="原始图片" class="detail-image" /> </div> <div class="image-item"> <p class="image-label">预测图片</p> <img :src="state.detailDialog.data.outImg" alt="预测图片" class="detail-image" /> </div> </div> </div> <div class="detail-section"> <h3 class="section-title">识别参数</h3> <el-descriptions :column="2" border> <el-descriptions-item label="识别权重"> <el-tag v-if="state.detailDialog.data.weight" type="success"> {{ state.detailDialog.data.weight }} </el-tag> <span v-else class="empty-text">-</span> </el-descriptions-item> <el-descriptions-item label="最小阈值"> <el-tag v-if="state.detailDialog.data.conf" type="warning"> {{ state.detailDialog.data.conf }} </el-tag> <span v-else class="empty-text">-</span> </el-descriptions-item> <el-descriptions-item label="AI助手"> <el-tag v-if="state.detailDialog.data.ai" type="info"> {{ state.detailDialog.data.ai }} </el-tag> <span v-else class="empty-text">-</span> </el-descriptions-item> <el-descriptions-item label="识别用户"> <el-tag v-if="state.detailDialog.data.username" type="primary"> {{ state.detailDialog.data.username }} </el-tag> <span v-else class="empty-text">-</span> </el-descriptions-item> <el-descriptions-item label="识别时间" :span="2"> {{ state.detailDialog.data.startTime || '-' }} </el-descriptions-item> </el-descriptions> </div> <div class="detail-section"> <h3 class="section-title">AI建议</h3> <el-card shadow="never" class="suggestion-card"> <div v-if="state.detailDialog.data.suggestion" class="suggestion-content"> {{ state.detailDialog.data.suggestion }} </div> <div v-else class="empty-text">暂无AI建议</div> </el-card> </div> <div class="detail-section"> <h3 class="section-title">详细识别结果</h3> <el-table :data="state.detailDialog.data.family" class="detail-table" empty-text="暂无详细数据"> <el-table-column prop="label" label="识别结果" align="center" /> <el-table-column prop="confidence" label="置信度" align="center" width="150"> <template #default="scope"> <div class="confidence-cell"> <el-progress :text-inside="true" :stroke-width="20" :percentage="Math.round(scope.row.confidence * 100)" :status="getConfidenceStatus(scope.row.confidence)" style="width: 120px;" /> <span class="confidence-value">{{ (scope.row.confidence * 100).toFixed(1) }}%</span> </div> </template> </el-table-column> <el-table-column prop="startTime" label="识别时间" align="center" width="180" /> </el-table> </div> </div> <template #footer> <span class="dialog-footer"> <el-button @click="state.detailDialog.visible = false">关闭</el-button> <el-button type="primary" @click="state.detailDialog.visible = false"> 确定 </el-button> </span> </template> </el-dialog> <!-- 图片预览弹窗 --> <el-dialog v-model="state.previewDialog.visible" :title="state.previewDialog.title" width="60%" align-center class="image-preview-dialog"> <div class="preview-content"> <img :src="state.previewDialog.imageUrl" :alt="state.previewDialog.title" class="preview-image" /> </div> </el-dialog> </div> </template> <script setup lang="ts"> import { defineAsyncComponent, reactive, onMounted, ref } from 'vue'; import { ElMessageBox, ElMessage } from 'element-plus'; import request from '/@/utils/request'; import { useUserInfo } from '/@/stores/userInfo'; import { storeToRefs } from 'pinia'; const stores = useUserInfo(); const { userInfos } = storeToRefs(stores); const state = reactive({ tableData: { data: [] as any, total: 0, loading: false, param: { search: '', search1: '', search2: '', pageNum: 1, pageSize: 10, }, }, detailDialog: { visible: false, title: '识别记录详情', data: null as any, }, previewDialog: { visible: false, title: '', imageUrl: '', }, }); // 获取表格数据 const getTableData = () => { state.tableData.loading = true; if (userInfos.value.userName != 'admin') { state.tableData.param.search = userInfos.value.userName; } request .get('/api/imgRecords', { params: state.tableData.param, }) .then((res) => { if (res.code == 0) { state.tableData.data = []; setTimeout(() => { state.tableData.loading = false; }, 500); for (let i = 0; i < res.data.records.length; i++) { const confidences = JSON.parse(res.data.records[i].confidence); const labels = JSON.parse(res.data.records[i].label); const transformedData = transformData(res.data.records[i], confidences, labels); transformedData["num"] = i + 1 + (state.tableData.param.pageNum - 1) * state.tableData.param.pageSize; state.tableData.data[i] = transformedData; } state.tableData.total = res.data.total; } else { ElMessage({ type: 'error', message: res.msg, }); } }); }; // 数据转换 const transformData = (originalData: any, confidences: any, labels: any) => { const family = labels.map((label: string, index: number) => ({ label: label, confidence: confidences[index], startTime: originalData.startTime })); const result = { id: originalData.id, inputImg: originalData.inputImg, outImg: originalData.outImg, weight: originalData.weight, allTime: originalData.allTime, conf: originalData.conf, startTime: originalData.startTime, username: originalData.username, ai: originalData.ai, suggestion: originalData.suggestion, family: family }; return result; } // 查看详情 const onViewDetail = (row: any) => { state.detailDialog.data = row; state.detailDialog.visible = true; }; // 图片预览 const previewImage = (imageUrl: string, title: string) => { state.previewDialog.imageUrl = imageUrl; state.previewDialog.title = title; state.previewDialog.visible = true; }; // 获取置信度状态 const getConfidenceStatus = (confidence: number) => { if (confidence >= 0.8) return 'success'; if (confidence >= 0.6) return 'warning'; return 'exception'; }; // 删除记录 const onRowDel = (row: any) => { ElMessageBox.confirm(`此操作将永久删除该识别记录,是否继续?`, '提示', { confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning', }) .then(() => { request.delete('/api/imgRecords/' + row.id).then((res) => { if (res.code == 0) { ElMessage({ type: 'success', message: '删除成功!', }); getTableData(); } else { ElMessage({ type: 'error', message: res.msg, }); } }); }) .catch(() => {}); }; // 分页改变 const onHandleSizeChange = (val: number) => { state.tableData.param.pageSize = val; getTableData(); }; // 分页改变 const onHandleCurrentChange = (val: number) => { state.tableData.param.pageNum = val; getTableData(); }; // 页面加载时 onMounted(() => { getTableData(); }); </script> <style scoped lang="scss"> .recognition-container { padding: 20px; background: #f5f7fa; min-height: calc(100vh - 84px); .recognition-content { max-width: 100%; margin: 0 auto; } .search-section { margin-bottom: 20px; .search-card { border-radius: 12px; border: none; :deep(.el-card__body) { padding: 20px; } .search-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; h3 { margin: 0; color: #303133; font-size: 16px; font-weight: 600; } .refresh-btn { background: #409EFF; border: none; border-radius: 6px; } } .search-controls { display: flex; align-items: center; justify-content: space-between; gap: 16px; .search-inputs { display: flex; gap: 12px; flex: 1; .search-field { flex: 1; max-width: 300px; :deep(.el-input__wrapper) { border-radius: 8px; } } } .search-btn { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; border-radius: 8px; padding: 0 20px; height: 36px; &:hover { background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%); transform: translateY(-1px); box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); } } } } } .data-section { .table-card { border-radius: 12px; border: none; :deep(.el-card__header) { border-bottom: 1px solid #f0f0f0; padding: 16px 20px; background: #fafafa; } .table-header { display: flex; justify-content: space-between; align-items: center; span { font-size: 16px; font-weight: 600; color: #303133; } } } .recognition-table { :deep(th) { background: #f8fafc; color: #374151; font-weight: 600; border-bottom: 1px solid #e5e7eb; } :deep(td) { border-bottom: 1px solid #f1f5f9; } :deep(.el-table__row:hover) { background: #f8fafc; } .img-wrapper { position: relative; display: flex; justify-content: center; align-items: center; padding: 4px; cursor: pointer; border-radius: 6px; overflow: hidden; &:hover { .img-overlay { opacity: 1; } img { transform: scale(1.05); } } img { width: 100px; height: 70px; object-fit: cover; border-radius: 4px; border: 1px solid #e5e7eb; transition: transform 0.3s ease; } .img-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; opacity: 0; transition: opacity 0.3s ease; .el-icon { color: white; font-size: 24px; } } } .suggestion-cell { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .empty-text { color: #909399; font-style: italic; } .action-buttons { display: flex; gap: 8px; justify-content: center; .view-btn, .delete-btn { border-radius: 4px; } } } .detail-panel { padding: 16px; background: #f8fafc; border-radius: 8px; margin: 8px; .detail-title { margin: 0 0 12px 0; color: #374151; font-size: 15px; font-weight: 600; } .nested-table { background: white; border-radius: 6px; overflow: hidden; :deep(th) { background: #f1f5f9; } .confidence-cell { display: flex; align-items: center; justify-content: center; gap: 8px; } } } .pagination-bar { margin-top: 20px; justify-content: center; padding: 16px; } } // 详情弹窗样式 .detail-dialog { .detail-content { max-height: 60vh; overflow-y: auto; padding-right: 8px; .detail-section { margin-bottom: 24px; .section-title { font-size: 16px; font-weight: 600; margin-bottom: 12px; color: #303133; padding-bottom: 8px; border-bottom: 1px solid #f0f0f0; } } .image-comparison { display: flex; gap: 20px; justify-content: center; .image-item { text-align: center; .image-label { margin-bottom: 8px; font-weight: 500; color: #606266; } .detail-image { width: 300px; height: 200px; object-fit: cover; border-radius: 8px; border: 1px solid #e5e7eb; } } } .suggestion-card { background: #f8f9fa; border: 1px solid #e9ecef; .suggestion-content { line-height: 1.6; color: #495057; } } .detail-table { :deep(th) { background: #f8f9fa; } .confidence-cell { display: flex; align-items: center; gap: 8px; .confidence-value { font-weight: 500; color: #409EFF; } } } } } // 图片预览弹窗样式 .image-preview-dialog { .preview-content { display: flex; justify-content: center; align-items: center; .preview-image { max-width: 100%; max-height: 70vh; object-fit: contain; border-radius: 8px; } } } } @media (max-width: 768px) { .recognition-container { padding: 12px; .search-section { .search-card { .search-controls { flex-direction: column; align-items: stretch; .search-inputs { flex-direction: column; .search-field { max-width: 100%; } } } } } .data-section { .recognition-table { :deep(.el-table) { overflow-x: auto; } } } .detail-dialog { .image-comparison { flex-direction: column; align-items: center; } } } } </style>后端代码展示
项目源码+数据集下载链接
完整代码在哔哩哔哩视频下方简介内获取:
基于YOLOv8/v10/v11/v12与SpringBoot的前后端分离花生种子霉变识别检测系统(DeepSeek智能分析+web交互界面)_哔哩哔哩_bilibili
基于YOLOv8/v10/v11/v12与SpringBoot的前后端分离花生种子霉变识别检测系统(DeepSeek智能分析+web交互界面)_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1gACtBTEHe/?spm_id_from=333.1387.homepage.video_card.click&vd_source=549d0b4e2b8999929a61a037fcce3b0f
https://www.bilibili.com/video/BV1gACtBTEHe
项目安装教程
https://www.bilibili.com/video/BV1YLsXzJE2X/?spm_id_from=333.1387.homepage.video_card.click
YOLO+spring boot+vue项目环境部署教程(YOLOv8、YOLOv10、YOLOv11、YOLOv12)_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1YLsXzJE2X/?spm_id_from=333.1387.homepage.video_card.click&vd_source=549d0b4e2b8999929a61a037fcce3b0f