赞
踩
如果你想了解YOLOv8的模型细节和里面每个流程,可以阅读这篇博客https://blog.csdn.net/Albert233333/article/details/130044349
如果这篇博客对你有帮助,希望你 点赞、收藏、关注、评论,您的认可将是我创作下去最大的动力!
MOT17——https://motchallenge.net/data/MOT17/
MOT15——https://motchallenge.net/data/MOT15/
MOTS——https://motchallenge.net/data/MOTS/
STEP-ICCV21——https://motchallenge.net/data/STEP-ICCV21/
YOLOv8+tracking+counter拿汽车那个视频,数出车数(过线才计数);自己再数一遍过线人数;用二者算MSE
- # 识别汽车
- python track.py --classes 2 --source ./val_data/Traffic.mp4
-
- # 识别人类
- python track.py --classes 0 --source ./val_data/dandong.mp4
-
- # 0是人类,2是汽车
- # 你写了0,那么只有人类可以被识别出来。识别都识别不到的东西,更不要谈计数了。
v5的那个算法的问题在于,他重识别re-identification做的不好,经常会有一个同样的人被识别成两个人,造成了数出来人数多很多
truth | v8_predict | v5_predict | ||
1 | Traffic.mp4 | 11 | 11 | |
2 | dandong.mp4 | 8 | 14 | |
3 | MOT17-09-SDP | 人太多数不过来(一下子进来很多人,我数不出来,应该拆成几个小的来数) | 19 | 34 |
4 | MOT17-04-SDP | 23 | 49 | |
5 | MOT17-02-SDP | 17 | 26 | |
6 | MOT17-08-DPM | 18 | 23 | |
7 | MOT17-01-FRCNN | 5 | 11 | |
8 | KITTI-17 | 9 | 14 | |
9 | TUD-Campus | 10 | 11 | |
10 | TUD-Stadtmitte | 1 | 0 | |
11 | Venice-1 | 5 | 11 | |
12 | KITTI-19(镜头移动速度过快,很难数) | 65 | 137 | |
13 | KITTI-16 | 13 | 51 | |
14 | ADL-Rundle-3 | 18 | 23 | |
15 | AVG-TownCentre(人群移动速度过快,很难数) | 92 | 88 | |
16 | PETS09-S2L2 | 19 | 24 | |
17 | TUD-Crossing | 13 | 28 |
可以用的MOT数据集(静止摄像头+是数人群)
MOT17-09-SDP——https://motchallenge.net/vis/MOT17-09-SDP/raw/
MOT17-04-SDP——https://motchallenge.net/vis/MOT17-04-SDP
MOT17-02-SDP——https://motchallenge.net/vis/MOT17-02-SDP
MOT17-08-DPM——https://motchallenge.net/vis/MOT17-08-DPM
MOT17-01-FRCNN——https://motchallenge.net/vis/MOT17-01-FRCNN
KITTI-17——https://motchallenge.net/vis/KITTI-17
TUD-Campus——https://motchallenge.net/vis/TUD-Campus
TUD-Stadtmitte——https://motchallenge.net/vis/TUD-Stadtmitte
Venice-1——https://motchallenge.net/vis/Venice-1
KITTI-19——https://motchallenge.net/vis/KITTI-19
KITTI-16——https://motchallenge.net/vis/KITTI-16
ADL-Rundle-3——https://motchallenge.net/vis/ADL-Rundle-3
AVG-TownCentre——https://motchallenge.net/vis/AVG-TownCentre
PETS09-S2L2——https://motchallenge.net/vis/PETS09-S2L2
TUD-Crossing——https://motchallenge.net/vis/TUD-Crossing
一个20秒的视频,有多少帧的图片需要标注?20秒的视频,600张图片。1秒钟,共计30张图片。假设你录制了10分钟的视频,就有1.8万张图片需要标注;1分钟的视频,就有1800张图片需要标注。
他官方给的标注,方框的id不能用——因为就算我人工打标注,我也标注不出来远处那么小的人啊!
后面很多很小的人,识别不出来。问题是我自己打标注,后面那些小人,我也识别不出
序号 | 数据集 | ground truth | v8 pr | |
1 | MOT17-01-FRCNN | 24 | 10 | |
2 | MOT17-02-SDP | 62 | 23 |
以第二个视频为例,我的YOLOv8模型识别出来是镜头前面这四五个人
这是官方提供的识别结果。就连远处那么模糊、那么变形不像个人的样子的人都识别出来了。但是官方提供的这些识别结果已经过于精细,超出了我们的需求。浪费算力识别出人眼都看不到的人,实在没必要。
对我有用的数据标注是什么?不同帧截图的标注,给每一个object赋予了独一无二的id;跨不同帧的截图,同一个object,被赋予了相同的id
MOT16
这个位置“MOT16/train/MOT16-02/det/det.txt”
每行一个标注,代表一个检测的物体。
上面每行数字中的含义是:
<frame>,<id>,<bb_left>,<bb_top>,<bb_width>,<bb_height>,<conf>,<x>,<y>,<z>
第一个代表这是第几帧图。上图显示的是第一帧和第二帧图上的标注
第二个代表轨迹编号(在这个文件里总是为-1),
bb开头的4个数(<bb_left>,<bb_top>,<bb_width>,<bb_height>)代表物体框的左上角坐标及长宽。
conf代表置信度
最后3个是MOT3D用到的内容,2D检测总是为-1.
https://blog.csdn.net/weixin_55775980/article/details/124369512
MOT16/train/MOT16-02/gt/gt.txt
第一个代表第几帧<frame>。第1帧到第594帧,一帧一条数据。如果一个object自始至终都出现,比如track=1,它的帧数就是从1到600.如果一个object只在特定的几帧中出现,那么这个track只显示它出现的那些帧的数量(track=74,它只出现在591-594这四帧)
第二个值为目标运动轨迹的ID号<object_id>。ID号从1到74,(但是MOT16官网上,显示MOT16-02这个数据集的Tracks的数量为54个,我猜多出来那20个是其他类别)
第三个到第六个数代表物体框的左上角坐标及长宽。<bb_left_x>,<bb_top_y>,<bb_width_x>,<bb_height_y>
第七个值为目标轨迹是否进入考虑范围内的标志,0表示忽略,1表示active。有0,有1.<cons>
第八个值为该轨迹对应的目标种类(种类见下面的表格中的label-ID对应情况).<label_id>
第九个值为box的visibility ratio,表示目标运动时被其他目标box包含/覆盖或者目标之间box边缘裁剪情况。<visi_r>
这个label-ID中哪些有可能是“人类”,这些类别都是可用的
pedestrian(id=1);Person on vehicle(id=2);static person(id=7)
MOT16-02数据集,gt.txt中track的数量有74个。在MOT16网站上,官方标注的54个人,用什么label做筛选能筛出这54个人?用你筛选的方法,把track 的id也给我。
- import pandas as pd
- dt = pd.read_csv("gt.csv",header=None)
- cl_name = ["frame","object_id","bb_left_x","bb_top_y","bb_width_x","bb_height_y","consider","label_id","visi_r"]
- dt.columns = cl_name
- len(dt[dt["label_id"]==7]["object_id"].unique())
- len(dt[dt["label_id"]==1]["object_id"].unique())
- len(dt[dt["label_id"]==2]["object_id"].unique())
static person(id=7)——9个
Person on vehicle(id=2)——3个
pedestrian(id=1)——54个
打开一张原图,找到作者标记的那个方框的位置,判断一下7和2,是否可以算作行人?是否可用,不可用那么我在自己的代码里就不用这两个label的了。
static person(id=7)。object_id为这9个的object是id=7这个类[ 1, 24, 25, 30, 45, 46, 47, 65, 66]。拿出object_id=1和object_id=45的目标拿出来看看长什么样?
MOT16-02的第一帧,这个位置有一个object,object_id=1,类别是“static person”
左上角(912,484)
右上角(912+97,484)=(1009,484)
左下角(912,484+109)= (912,593)
右下角(912+97,484+109)= (1009,593)
这个地方果真有个坐着的男人,和label的“static person”是一致的
MOT16-02第600帧,这个位置有一个object,object_id=45,类别是“static person”
左上角(668,462)
右上角(668+25,462)=(693,462)
左下角(668,462+72) = (668,534)
右下角(668+25,462+72)= (693,534)
static person(id=7)可以算进去
MOT16-02第1帧,这个位置有一个object,object_id=11,类别是“Person on vehicle”
坐标是
左上角(734,487)
图中是这样
是一个骑着自行车的人
Person on vehicle(id=2)可以算进去
确认MOT17
(1)MOT17数据集,只有训练集有追踪的id标注,测试集是没有的
(2)视频的三种识别方法SDP、DPM、FRCNN,识别出来的track数量都是一样的吗?一样的话,才可以随意挑一个出来用——是一样的,你挑一个用就行了
拿到原始视频
原始视频输入YOLOv8,拿到模型数出的数字
- python track.py --classes 0 --source ./val_data/with_tracking_gt/MOT17-02-FRCNN-raw.mp4
- # 记得要使用过线才计数的功能,而不是出现就计数
调用官方提供的gt文件,写一个循环,判断这段视频里面,有行人(包括三类)的纵坐标大于0.7*w
标注文件收集完毕
想一下每个标注文件怎么逐行读取,拿到有行人(包括三类)的纵坐标大于0.7*w
label_id= 7,2,1都是可以的
static person(id=7)
Person on vehicle(id=2)
pedestrian(id=1)
- # 拿到文件夹列表
- import os
- dt_list = os.listdir('./')
- dt_list.remove(".ipynb_checkpoints")
- dt_list.remove("get_gt_count.ipynb")
- dt_list
-
- # ['MOT17-02-FRCNN',
- # 'MOT17-04-FRCNN',
- # 'MOT17-05-FRCNN',
- # 'MOT17-09-FRCNN',
- # 'MOT17-10-FRCNN',
- # 'MOT17-11-FRCNN',
- # 'MOT17-13-FRCNN']
-
- import pandas as pd
- for dataset_dir_name in dt_list:
- # 用于存储那些过线的id的列表
- overline_list = []
-
- # 取出视频的meta信息,把视频的height高度取出来,方便后面乘以70%
- seqinfo = pd.read_csv('./'+dataset_dir_name+'/seqinfo.ini')
- # 这就是图片的高度
- # 这一列[Sequence]
- # index=5的这一行
- # 从“imHeight=”的等于号这里向后取,就是画面的高度
- pic_height = int(seqinfo['[Sequence]'][5][9:])
-
- # 写好gt文件的路径
- gt_file_path = './'+dataset_dir_name+'/gt/gt.txt'
- # 读取标注文件
- dt = pd.read_csv(gt_file_path,header=None)
- # 加上表头
- cl_name = ["frame","object_id","bb_left_x","bb_top_y","bb_width_x","bb_height_y","consider","label_id","visi_r"]
- dt.columns = cl_name
- # 删除掉那些对这个计数任务无关的数据(列)
- dt = dt.drop(["bb_left_x","bb_width_x","consider","visi_r"],axis=1)
-
- # label id的数字不同,表示object类别不同
- # id =1是pedestrian,id=2是person on vehicle,id=7是static person,id=3是汽车,id=4是自行车
- # 我们计数仅仅统计是 1,2,7这些是人类的,其他类别的,一律不要整行删掉
-
- # id=1行人在这三类中占的比重最大,2和7这两类好几个数据集里面压根就没有这两个类别,所以先把id=1的这些行拿出来
- dt_fin = dt[(dt['label_id']==1)]
- # 如果存在id=2或7再拼接上去
- if dt[dt.label_id==2].empty:
- pass
- else:
- dt_fin = pd.concat([dt_fin,dt[dt.label_id==2]])
-
- if dt[dt.label_id==7].empty:
- pass
- else:
- dt_fin = pd.concat([dt_fin,dt[dt.label_id==7]])
-
-
-
- # 逐一遍历每一个object_id的小dataframe,
- # 对于每个小dataframe,逐行遍历,看top+height和top,中间有一个纵坐标大于0.7*pic_height,就把这个object_id放进计数列表里面
-
- # 所有需要遍历的object_id的列表
- iter_list = dt_fin["object_id"].unique()
- # 所有需要遍历的object_id逐个遍历,每次处理一个object_id相同的dataframe
- for iter_num in iter_list:
- df_same_obj_id = dt_fin[dt_fin["object_id"]==iter_num]
-
- # 一个object_id全相同的,小的dataframe里面再逐行遍历
- for index, row in df_same_obj_id.iterrows():
- bottom_y = row["bb_top_y"]+row["bb_height_y"]
- top_y = row["bb_top_y"]
- # 上下纵坐标中有一个大于pic_height*0.7,就计入计数列表
- if_cross_line = ((bottom_y > pic_height*0.7) or (top_y > pic_height*0.7))
-
- if if_cross_line and (iter_num not in overline_list):
- overline_list.append(iter_num)
- # 既然这个id中有一次过线了,那么这个id后面位置是否过线我就不用管里.直接退出这个循环,外面id循环下一个object_id
- break
-
- # 显示这个数据集,有多少个过线的人类,id列表是什么?
- print("#"*100)
- print("In %s, there are \n%s\n persons cross the 0.30 line. The object ID list is"%(dataset_dir_name,len(overline_list)))
- print(overline_list)
- print("#"*100)
- print("\n")
- # print(dt_fin)
从此拿到基于MOT17数据集作者标注的计数结果
- ####################################################################################################
- In MOT17-02-FRCNN, there are
- 14
- persons cross the 0.30 line. The object ID list is
- [2, 3, 8, 16, 19, 20, 23, 32, 33, 34, 35, 36, 37, 38]
- ####################################################################################################
-
-
- ####################################################################################################
- In MOT17-04-FRCNN, there are
- 21
- persons cross the 0.30 line. The object ID list is
- [1, 3, 5, 6, 70, 71, 80, 82, 83, 89, 98, 99, 100, 101, 102, 103, 104, 106, 122, 123, 125]
- ####################################################################################################
-
-
- ####################################################################################################
- In MOT17-05-FRCNN, there are
- 65
- persons cross the 0.30 line. The object ID list is
- [1, 2, 17, 20, 23, 25, 26, 29, 30, 31, 32, 34, 35, 36, 37, 41, 45, 46, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 69, 70, 71, 72, 73, 74, 76, 77, 80, 81, 82, 86, 88, 91, 93, 94, 95, 97, 100, 102, 123, 124, 125, 126, 127, 128, 129, 154, 116]
- ####################################################################################################
-
-
- ####################################################################################################
- In MOT17-09-FRCNN, there are
- 15
- persons cross the 0.30 line. The object ID list is
- [1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 19, 20, 23, 29]
- ####################################################################################################
-
-
- ####################################################################################################
- In MOT17-10-FRCNN, there are
- 9
- persons cross the 0.30 line. The object ID list is
- [1, 3, 7, 9, 10, 12, 18, 24, 25]
- ####################################################################################################
-
-
- ####################################################################################################
- In MOT17-11-FRCNN, there are
- 34
- persons cross the 0.30 line. The object ID list is
- [1, 2, 3, 4, 5, 6, 7, 10, 12, 14, 20, 24, 25, 27, 32, 33, 34, 35, 36, 37, 38, 39, 41, 42, 44, 45, 48, 49, 56, 59, 62, 80, 89, 90]
- ####################################################################################################
-
-
- ####################################################################################################
- In MOT17-13-FRCNN, there are
- 39
- persons cross the 0.30 line. The object ID list is
- [2, 5, 6, 8, 9, 10, 11, 14, 15, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 33, 38, 47, 48, 49, 52, 53, 70, 133, 135, 139, 144, 147, 148, 155, 157, 160, 161, 165]
- ####################################################################################################
二者对比算出MSE
我怀疑YOLOv5用的识别和追踪算法就是MOT17官方使用的数据集的这个FRCNN方法,所以才计数结果完全一样——动态摄像头 YOLOv5数的很准,
静态摄像头,YOLOv5数出来的数字偏大
序号 | video | ground truth | v8 | v5 |
1 | MOT17-13-FRCNN | 39 | 27 | 39 |
2 | MOT17-11-FRCNN | 34 | 31 | 34 |
3 | MOT17-10-FRCNN | 9 | 7 | 9 |
4 | MOT17-09-FRCNN | 15 | 19 | 34 |
5 | MOT17-05-FRCNN | 65 | 54 | 83 |
6 | MOT17-04-FRCNN | 21 | 23 | 49 |
7 | MOT17-02-FRCNN | 14 | 17 | 26 |
- y_true = [39,34,9,15,65,21,14]
- p_v8 = [27,31,7,19,54,23,17]
- p_v5 = [39,34,9,34,83,49,26]
-
- # 使用sklearn调用衡量线性回归的MSE 、 RMSE、 MAE、r2
- from math import sqrt
- from sklearn.metrics import mean_absolute_error
- from sklearn.metrics import mean_squared_error
- from sklearn.metrics import r2_score
-
- print("YOLOv5")
- print("MAE:", mean_absolute_error(y_true, p_v5))
- print("MSE:", mean_squared_error(y_true, p_v5))
- print("RMSE:", sqrt(mean_squared_error(y_true, p_v5)))
- print("r2 score:", r2_score(y_true, p_v5))
- print("\n")
-
- print("YOLOv8")
- print("MAE:", mean_absolute_error(y_true, p_v8))
- print("MSE:", mean_squared_error(y_true, p_v8))
- print("RMSE:", sqrt(mean_squared_error(y_true, p_v8)))
- print("r2 score:", r2_score(y_true, p_v8))
-
- # YOLOv5
- # MAE: 11.0
- # MSE: 230.42857142857142
- # RMSE: 15.179873893697913
- # r2 score: 0.29895691046814854
-
-
- # YOLOv8
- # MAE: 5.285714285714286
- # MSE: 43.857142857142854
- # RMSE: 6.622472563713862
- # r2 score: 0.8665714640506643
MSE MAE计算
MSE | MAE | RMSE | r2 | |
v5 | 230.43 | 11.0 | 15.18 | 0.2990 |
v8 | 43.86 | 5.28 | 6.62 | 0.8666 |
你确定一下MOT这几个数据集里,可以做行人计数的数据集里面,那些是有ground truth的?有的统统拿来
其他测试集上有没有 track id,可否判断是哪个object id 可以知道他们是哪个object,位置在哪?
yolov5中只有deepSORT,yolov8数据集的那三种追踪算法你也用到YOLOv5上面
deepSORT只用在了YOLOv5上,v8上你也要用上。
然后两种识别识别算法,每种识别算法配上5种追踪算法,一共十种组合,你把这十个模型组合的分数拿出来,对比一下你才能说上面这个分数的提高是v5-v8带来的还是追踪算法带来的。
作者在eval.py这个代码里面,似乎定义了(1)怎么下载MOT数据集,(2)下载哪些评估追踪的评估指标,如何得出评估结果
所有的评估比赛的汇总和评估指标的计算这里都有定义https://github.com/JonathonLuiten/TrackEval
- # 实际是下载MOT16数据集,对追踪效果做评估
- python val.py --tracking-method strongsort --benchmark MOT16
运行后报错
(记得运行的时候一定要关闭,展示结果;因为MOT数据集下载下来是一张张图片;一个个展示会开很多个图片显示窗口,会很卡的)
** The following layers are discarded due to unmatched keys or layer size: ['classifier.weight', 'classifier.bias']
Traceback (most recent call last):
File "track.py", line 516, in <module>
main(opt)
File "track.py", line 511, in main
run(**vars(opt))
File "/home/albert/anaconda3/envs/py380tc170/lib/python3.8/site-packages/torch/autograd/grad_mode.py", line 26, in decorate_context
return func(*args, **kwargs)
File "track.py", line 362, in run
start_point = (0, int(h-0.3*h)) # 距离照片最底端留下30%
UnboundLocalError: l ocal variable 'h' referenced before assignment
Traceback (most recent call last):
File "val_utils/scripts/run_mot_challenge.py", line 84, in <module>
dataset_list = [trackeval.datasets.MotChallenge2DBox(dataset_config)]
File "/media/F:/FILES_OF_ALBERT/IT_paid_class/graduation_thesis/model_innov/yolov8_tracking/val_utils/trackeval/datasets/mot_challenge_2d_box.py", line 124, in __init__
raise TrackEvalException(
trackeval.utils.TrackEvalException: Tracker file not found: MOT16/exp2/data/MOT16-02.txt
Tracker file not found: /media/F:/FILES_OF_ALBERT/IT_paid_class/graduation_thesis/model_innov/yolov8_tracking/val_utils/data/trackers/mot_challenge/MOT16/exp2/data/MOT16-02.txt
Traceback (most recent call last):
File "val.py", line 369, in <module>
e.run(opt)
File "val.py", line 321, in run
writer.add_scalar('HOTA', combined_results['HOTA'])
KeyError: 'HOTA'
我怀疑ultralytics的YOLOv8新出的追踪功能,也具备评估追踪功能好坏的指标——你去查查有没有这功能?
最终的评估指标有两个(1)识别的评估指标和追踪的评估指标是评价整个画面的数据。(2)计数是只数低端画面30%的人数
所有的评估比赛的汇总和评估指标的计算这里都有定义https://github.com/JonathonLuiten/TrackEval
https://blog.csdn.net/weixin_55775980/article/details/124369512#t3
3. 指标计算
3.1 基础评测指标
1、ID Switches (ID Sw.): 被跟踪目标身份发生错误地更改时被称为身份跳变。在对视频序列的跟踪过程当中,ID Sw.表示所有跟踪目标身份交换的次数。
2、Frames Per Second (FPS): 多目标跟踪器的跟踪速度。
3、False Positives (FP): 在视频序列中不能与 真实轨迹的边界框 相匹配的 假设边界框 被称为假阳性;即本来是假的,预测认为是真的。FP 表示整个视频序列中假阳性的数量。
4、False Negatives (FN): 在视频序列中不能与 假设边界框 相匹配的 真实轨迹的边界框 被称为假阴性;即本来是真的,预测认为是假的。FN 表示整个视频序列中假阴性的数量。
5、Mostly Tracked tracklets (MT):在跟踪过程中各个目标至少有 80%的视频帧都能被正确地跟踪的跟踪轨迹数量。
6、Mostly Lost tracklets (ML):在跟踪过程中各个目标至多有 20%的视频帧能被正确地跟踪的跟踪轨迹数量。
7、Fragments (Frag):真实跟踪轨迹由于某种原因发生中断并在后来又继续被准确跟踪上被称为跟踪碎片。Frag 表示整个视频序列中碎片的总数。
以上 7 种评测指标主要度量多目标跟踪器的基础性能,其中加粗的比较常用;为了进一步评判多目标跟踪器的综合性能,出现了其他指标。
3.2 MOTA和MOTP
CLEAR MOT 指标提出了 多目标跟踪精度MOTA 和 多目标跟踪准确度MOTP 两个综合性的指标,这两个指标能够衡量多目标跟踪器在整体上的性能好坏。
MOTA(Multiple Object Tracking Accuracy) :MOTA主要考虑的是tracking中所有对象匹配错误,给出的是非常直观的衡量跟踪其在检测物体和保持轨迹时的性能,与目标检测精度无关,MOTA取值小于100,但是当跟踪器产生的错误超过了场景中的物体,MOTA可以变为负数。
MOTP(Multiple Object Tracking Precision) : 是使用bonding box的overlap rate来进行度量(在这里MOTP是越大越好,但对于使用欧氏距离进行度量的就是MOTP越小越好,这主要取决于度量距离d的定义方式) MOTP主要量化检测器的定位精度,几乎不包含与跟踪器实际性能相关的信息。
MOTA&MOTP是计算所有帧相关指标后再进行平均的
3.3 IDP、IDR、IDF
IDP : 识别精确度
整体评价跟踪器的好坏,识别精确度 IDP 的分数如下进行计算:
IDR:识别召回率
它是当IDF1-score最高时正确预测的目标数与真实目标数之比,识别召回率 IDR 的分数如下进行计算:
IDF1:平均数比率
IDF1是指正确的目标检测数与真实数和计算检测数和的平均数比率,这里, IDF1的分数如下进行计算:
I D F 1 = I D T P I D T P + 0.5 I D F P + 0.5 I D F N IDF1=\frac{I D T P}{I D T P+0.5 I D F P+0.5 I D F N}
IDF1=
IDTP+0.5IDFP+0.5IDFN
IDTP
上述公式中,IDTP 可以看作是在整个视频中检测目标被正确分配的数量,IDFN 在整个视频中检测目标被漏分配的数量,IDFP 在整个视频中检测目标被错误分配的数量。
————————————————
版权声明:本文为CSDN博主「一个小呆苗」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_55775980/article/details/124369512
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。