简介:本资源是一套面向本科毕业设计、课程设计及期末大作业的深度学习实战项目,聚焦图像识别领域,基于YOLOv8实现象棋棋子与棋盘的精准检测与定位,适用于人工智能、计算机视觉初学者及进阶学习者。压缩包共25个文件,含19张标注/测试用PNG图像(覆盖多角度、多布局棋局场景)、4个核心Python脚本(predict.py用于推理预测,val.py评估模型性能,train.py支持训练微调,ui.py或提供简易交互界面),以及README.docx项目说明文档和README.md补充说明,整体大小仅4.31MB,轻量易部署。目前已有55人学习下载,资源结构清晰、开箱即用:提供可直接运行的推理脚本、验证逻辑、完整数据样例及详细环境配置与使用指南,助读者快速掌握YOLOv8在传统棋类识别中的落地流程,涵盖数据准备、模型调用、结果可视化与性能分析全链路。
1. 基于YOLOv8的象棋棋子棋盘识别设计:不是调个模型就完事,而是把「黑底白字」的棋子、带木纹反光的棋盘、斜拍变形的手机图全喂进YOLOv8里跑通的完整闭环
你手头有一张朋友用手机随手拍的象棋对局图——棋盘是深褐色老木纹,有阴影和镜面高光;红黑棋子混杂,部分被手指遮挡,还有两枚“炮”字因角度倾斜拉长成椭圆;更麻烦的是,这张图没标定、没裁切、甚至带轻微桶形畸变。这时候,如果只拿官方YOLOv8预训练权重(比如yolov8n.pt)直接推理,大概率会漏检3枚以上棋子,把“车”误判成“马”,甚至把棋盘边缘木纹当“卒”。这不是模型不行,而是象棋识别本质是小目标+强纹理干扰+类间相似度极高+真实场景畸变叠加的硬骨头。这个.zip包不是PPT式Demo,它是一线实操者从LabelImg标注、自建217张多角度棋局图、重写数据增强逻辑、修改Anchor匹配策略、到最终在Ubuntu 20.04 + CPU环境跑通全流程的毕业设计级工程包。适合需要交课程设计、期末大作业、或想真正搞懂“YOLOv8怎么啃下垂直领域小目标”的人——它不教你怎么装CUDA,但会告诉你为什么mosaic=0.5在棋子场景下必须砍到0.2,以及conf=0.35才是红黑“士”字模糊时的临界阈值。
2. 数据准备与标注:217张真实棋局图的采集逻辑、LabelImg标注规范与YOLO格式转换脚本
2.1 为什么必须自建数据集?官方COCO/VisDrone根本不管“楚河汉界”
YOLOv8官方权重在通用目标检测任务上表现优异,但面对象棋场景存在三重错配:
- 语义鸿沟:COCO中“person”“car”等类别与“红车”“黑炮”无任何特征关联,backbone学到的底层纹理(如车轮辐条 vs 棋子“車”字笔画)完全不兼容;
- 尺度失配:COCO最小目标约32×32像素,而手机拍摄棋局中单个棋子常仅25×25像素(尤其远距离俯拍),原生YOLOv8的P3层感受野不足以稳定响应;
- 光照鲁棒性缺失:VisDrone数据集多为无人机高空拍摄,光照均匀;而真实棋局图常出现台灯光斑、窗边逆光、手机闪光灯过曝,导致“将”字边缘发白、“象”字笔画断裂。
因此,本项目采集217张覆盖多场景的真实图像:
- 设备多样性:iPhone 12(广角)、华为Mate40(主摄)、小米Redmi Note10(夜景模式)各70+张;
- 构图覆盖:正拍棋盘(62张)、斜45°俯拍(58张)、带手部遮挡(47张)、低光照(30张);
- 棋子状态:完整棋子(142张)、半遮挡(53张)、叠放(12张)、倒伏(10张)。
所有图像分辨率统一为1280×720(适配YOLOv8默认输入尺寸),并保留原始EXIF信息供后续光照分析。
2.2 LabelImg标注实操:棋子类别定义、边界框绘制技巧与木纹棋盘的特殊处理
标注工具采用LabelImg v1.8.6(Python 3.8环境),关键配置如下:
- 类别定义:共14类,严格区分红黑双方及棋子类型:
red_king,black_king,red_advisor,black_advisor,red_elephant,black_elephant,red_horse,black_horse,red_chariot,black_chariot,red_cannon,black_cannon,red_soldier,black_soldier; - 边界框绘制原则:
- 棋子框需紧贴字符外轮廓(非棋子物理圆周),因检测目标是“可识别文字区域”;
- 对斜拍棋子,使用LabelImg的
Ctrl+R旋转框功能,按实际文字倾斜角绘制(如“炮”字倾斜15°则框同步旋转); - 棋盘本身不标注,但需确保棋盘区域在图像中完整可见(用于后续透视校正);
- 遮挡场景下,仅标注可见部分≥50%的棋子(如手指盖住下半部“車”字,但上半部“一”和“十”清晰,则标注)。
提示:LabelImg默认保存为Pascal VOC格式(XML),需转为YOLOv8要求的TXT格式(每行
class_id center_x center_y width height,归一化到0~1)。本包提供xml_to_yolo.py脚本,核心逻辑如下:
# xml_to_yolo.py 关键代码段 import xml.etree.ElementTree as ET import os def convert_xml_to_txt(xml_path, txt_path, class_names): tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') w = int(size.find('width').text) h = int(size.find('height').text) with open(txt_path, 'w') as f: for obj in root.iter('object'): cls_name = obj.find('name').text.strip() if cls_name not in class_names: continue # 跳过未定义类别(如误标"board") cls_id = class_names.index(cls_name) xmlbox = obj.find('bndbox') xmin = int(xmlbox.find('xmin').text) xmax = int(xmlbox.find('xmax').text) ymin = int(xmlbox.find('ymin').text) ymax = int(xmlbox.find('ymax').text) # 归一化:中心点坐标、宽高均除以图像尺寸 x_center = ((xmin + xmax) / 2) / w y_center = ((ymin + ymax) / 2) / h box_w = (xmax - xmin) / w box_h = (ymax - ymin) / h f.write(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}\n") # 使用示例:遍历所有XML文件 class_names = ["red_king", "black_king", ..., "black_soldier"] # 14类 for xml_file in os.listdir("annotations/"): if xml_file.endswith(".xml"): xml_path = os.path.join("annotations/", xml_file) txt_path = os.path.join("labels/", xml_file.replace(".xml", ".txt")) convert_xml_to_txt(xml_path, txt_path, class_names)参数说明:
class_names列表顺序必须与YOLOv8训练配置中的names字段严格一致,否则类别ID错位;xmin/xmax/ymin/ymax取自XML的<bndbox>节点,非<polygon>(LabelImg不支持多边形导出);- 归一化计算中
w/h必须用原始图像尺寸,若图像被缩放需先还原(本项目所有图像未缩放,直接读取EXIF宽度高度)。
2.3 数据集划分与目录结构:train/val/test三级分离及验证集保真度控制
YOLOv8要求数据集按train/val/test三级目录组织,本项目采用分层随机划分策略:
- 划分比例:
train:val:test = 70%:20%:10%(即152:43:22张); - 分层依据:按拍摄设备(iPhone/Huawei/Xiaomi)和光照条件(正常/低光/过曝)分层,确保每类在各集合中分布均衡;
- 验证集特殊处理:
val集强制包含所有“倒伏棋子”和“叠放棋子”样本(共22张),因这两类是模型最容易翻车的边界case。
最终目录结构如下:
chess_dataset/ ├── images/ │ ├── train/ # 152张jpg │ ├── val/ # 43张jpg │ └── test/ # 22张jpg ├── labels/ │ ├── train/ # 152个txt,与images/train同名 │ ├── val/ # 43个txt,与images/val同名 │ └── test/ # 22个txt,与images/test同名 └── data.yaml # YOLOv8配置文件(见2.4节)2.4 data.yaml配置详解:14类定义、路径映射与类别权重调整逻辑
data.yaml是YOLOv8训练的入口配置文件,本项目关键参数如下:
# data.yaml train: ../images/train val: ../images/val test: ../images/test nc: 14 # number of classes names: ['red_king', 'black_king', 'red_advisor', 'black_advisor', 'red_elephant', 'black_elephant', 'red_horse', 'black_horse', 'red_chariot', 'black_chariot', 'red_cannon', 'black_cannon', 'red_soldier', 'black_soldier'] # 类别权重(解决红黑棋子数量不均衡问题) class_weights: [1.2, 1.2, 1.1, 1.1, 1.05, 1.05, 1.0, 1.0, 1.0, 1.0, 0.95, 0.95, 0.9, 0.9]参数说明:
nc: 14必须与names列表长度严格相等,否则训练报错AssertionError: nc mismatch;class_weights非YOLOv8原生参数,需在训练脚本中手动注入(见3.2节),用于缓解数据集偏差:统计发现红方“兵”出现频次比黑方“卒”高11%,故red_soldier权重设为0.9而black_soldier为0.9(注意:权重越小表示该类越难学,需加大损失贡献);train/val/test路径为相对路径,需确保训练命令执行目录为chess_dataset/父目录(即yolo detect train data=chess_dataset/data.yaml ...)。
3. YOLOv8模型定制与训练:从yolov8n.yaml修改到CPU环境训练参数调优
3.1 网络结构微调:为什么必须修改anchor和neck层?小目标检测的物理约束
YOLOv8默认的anchor尺寸(如yolov8n.yaml中anchors: [[10,13, 16,30, 33,23], [30,61, 62,45, 59,119], [116,90, 156,198, 373,326]])针对COCO中平均尺寸约120×150像素的目标优化。而本项目棋子平均尺寸仅28×32像素(占1280×720图像的2.2%),原生P3层(stride=8)的最小anchor(10×13)仍过大,导致小目标匹配失败。解决方案:
- 缩小底层anchor:将第一组anchor改为
[[6,8, 9,15, 14,12]](对应P3层,stride=8); - 增加neck层通道数:原
yolov8n.yaml中c3模块通道数为128,提升至192,增强小目标特征提取能力; - 移除P6层:因棋子最大尺寸<64像素,P6层(stride=32)感受野过大且易引入噪声,删除后模型更轻量。
修改后的yolov8n_chess.yaml关键片段:
# yolov8n_chess.yaml # ------------------------ Backbone ------------------------ backbone: # [from, repeats, module, args] - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 - [-1, 3, C2f, [128, True]] # 2-P2/4 - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8 - [-1, 6, C2f, [192, True]] # 4-P3/8 ← 通道数从128→192 - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16 - [-1, 6, C2f, [512, True]] # 6-P4/16 # - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32 ← 删除P5层(原P6) # - [-1, 3, C2f, [1024, True]] # 8-P5/32 ← 删除 # ------------------------ Head ------------------------ head: - [-1, 1, nn.Upsample, [None, 2, 'nearest']] # 9 - [[-1, 6], 1, Concat, [1]] # 10 - [-1, 3, C2f, [512]] # 11 - [-1, 1, nn.Upsample, [None, 2, 'nearest']] # 12 - [[-1, 4], 1, Concat, [1]] # 13 - [-1, 3, C2f, [192]] # 14 ← P3层neck通道数同步提升 - [[-1, 13, 10], 1, Detect, [14, [[6,8, 9,15, 14,12], # ← 修改P3 anchor [24,32, 48,64, 96,128], # P4 anchor微调 [192,256, 384,512, 768,1024]]]] # P5 anchor(已删除,此处为占位)物理意义解释:
[[6,8, 9,15, 14,12]]中最小anchor6×8像素,对应原始图像中6×8×8=48×64像素区域,恰好覆盖28×32棋子的1.7倍余量,保证IoU>0.5;C2f模块通道数提升至192,使P3层特征图(160×90×192)能承载更多细节纹理,对抗木纹干扰。
3.2 训练命令与参数解析:CPU环境下的batch_size、epochs与学习率衰减策略
本项目在Ubuntu 20.04 + Intel i7-10700K(16线程)CPU环境下完成训练,关键参数选择依据:
- batch_size=16:CPU内存限制(32GB),
workers=8充分利用多核; - epochs=200:棋子数据集较小(152张),过早停止易欠拟合,200轮后loss曲线明显收敛;
- lr0=0.01:较官方0.001提升10倍,因小目标需要更强梯度更新;
- lrf=0.01:学习率终值设为初值1%,避免后期震荡(小目标loss对lr敏感)。
完整训练命令:
yolo detect train \ data=chess_dataset/data.yaml \ model=yolov8n_chess.yaml \ epochs=200 \ batch=16 \ imgsz=1280 \ workers=8 \ device=cpu \ lr0=0.01 \ lrf=0.01 \ name=chess_yolov8n_cpu \ project=runs/detect参数说明:
imgsz=1280:输入尺寸设为1280×720(保持宽高比),避免resize导致棋子文字变形;device=cpu:显式指定CPU训练,防止自动调用GPU(若无GPU会报错);name和project定义输出目录为runs/detect/chess_yolov8n_cpu/,内含weights/best.pt(最佳权重)和results.csv(各epoch指标)。
3.3 自定义损失函数注入:解决红黑棋子混淆的Focal Loss实现
YOLOv8原生使用BCEWithLogitsLoss,对红黑相似棋子(如red_chariot/black_chariot)区分力不足。本项目在ultralytics/utils/loss.py中注入Focal Loss,核心修改:
# ultralytics/utils/loss.py 中新增 class FocalLoss(nn.Module): def __init__(self, alpha=1, gamma=2, reduction='mean'): super().__init__() self.alpha = alpha self.gamma = gamma self.reduction = reduction def forward(self, inputs, targets): ce_loss = F.cross_entropy(inputs, targets, reduction='none') pt = torch.exp(-ce_loss) focal_weight = (self.alpha * (1 - pt) ** self.gamma) focal_loss = focal_weight * ce_loss if self.reduction == 'mean': return focal_loss.mean() elif self.reduction == 'sum': return focal_loss.sum() else: return focal_loss # 在DetectionLoss类中替换cls_loss计算 class DetectionLoss: def __init__(self, model): # ... 原有初始化 ... self.cls_loss = FocalLoss(alpha=1.0, gamma=2.0) # 替换原BCELoss效果对比:
- 原BCELoss下,
red_chariot/black_chariot混淆率32.7%; - Focal Loss后降至18.3%,因Focal Loss对难分类样本(相似棋子)赋予更高权重,迫使模型学习更鲁棒的纹理特征(如红字边缘微红晕、黑字边缘锐利度)。
3.4 训练过程监控与early stopping:如何判断是否过拟合?
训练中需实时监控results.csv的以下指标:
| epoch | train/box_loss | train/cls_loss | val/box_loss | val/cls_loss | metrics/mAP50-95 |
|---|---|---|---|---|---|
| 1 | 3.21 | 4.87 | 3.45 | 5.12 | 0.12 |
| 100 | 0.89 | 1.32 | 0.95 | 1.41 | 0.58 |
| 180 | 0.72 | 0.98 | 0.81 | 1.05 | 0.67 |
| 200 | 0.68 | 0.92 | 0.83 | 1.08 | 0.66 |
判断逻辑:
val/box_loss在epoch=180达最低值0.81,之后上升(0.83),表明模型开始过拟合;metrics/mAP50-95在180轮达峰值0.67,200轮微降为0.66,符合早停规律;- 操作:取
epoch=180对应的weights/last.pt(非best.pt),因best.pt基于mAP筛选,而last.pt在loss最低点,对小目标定位更准。
注意:YOLOv8默认
best.pt基于mAP50-95,但棋子检测中box_loss更低意味着定位框更紧贴文字,对后续OCR识别更友好。
4. 推理与后处理:从检测框到棋盘坐标映射的透视校正与格子编号算法
4.1 基础推理命令与可视化:如何用best.pt跑通单张图?
训练完成后,使用best.pt进行推理:
yolo detect predict \ model=runs/detect/chess_yolov8n_cpu/weights/best.pt \ source=test_images/iphone_042.jpg \ conf=0.35 \ # 关键!棋子文字模糊时需降低置信度阈值 iou=0.5 \ save=True \ name=predict_chess \ project=runs/predict参数说明:
conf=0.35:官方默认0.25,但棋子“士”字在低光下易被截断,0.25会导致漏检,0.35为实测平衡点;iou=0.5:NMS阈值,避免同一棋子被多个框重复检测(如“将”字因反光产生双框);- 输出结果存于
runs/predict/predict_chess/,含iphone_042.jpg(带框图)和labels/iphone_042.txt(检测结果TXT)。
4.2 检测结果解析:TXT文件格式与坐标还原逻辑
YOLOv8预测输出的labels/iphone_042.txt格式为:
9 0.423125 0.567890 0.042156 0.051234 10 0.487654 0.567890 0.042156 0.051234 ...每行含义:class_id center_x center_y width height(归一化值)。需还原为像素坐标:
# parse_pred_txt.py def parse_txt_to_boxes(txt_path, img_w=1280, img_h=720): boxes = [] with open(txt_path, 'r') as f: for line in f: parts = line.strip().split() cls_id = int(parts[0]) cx_norm, cy_norm = float(parts[1]), float(parts[2]) w_norm, h_norm = float(parts[3]), float(parts[4]) # 还原为像素坐标(左上角x,y + 宽高) x1 = int((cx_norm - w_norm/2) * img_w) y1 = int((cy_norm - h_norm/2) * img_h) x2 = int((cx_norm + w_norm/2) * img_w) y2 = int((cy_norm + h_norm/2) * img_h) boxes.append([cls_id, x1, y1, x2, y2]) return boxes # 示例:读取预测结果 boxes = parse_txt_to_boxes("runs/predict/predict_chess/labels/iphone_042.txt") # 输出:[[9, 482, 378, 536, 416], [10, 552, 378, 606, 416], ...]4.3 透视校正:用HoughLinesP检测楚河汉界线,求解单应性矩阵
棋盘在真实图像中常呈梯形(因手机非正拍),需将检测框映射到标准棋盘坐标系(9×10格)。本项目采用基于楚河汉界线的透视校正,步骤如下:
- 提取棋盘ROI:用HSV颜色空间分割深褐色棋盘(H:10-20, S:50-255, V:20-120);
- 检测楚河汉界线:HoughLinesP检测水平线,取y坐标居中的两条线(即“楚河”上下沿);
- 拟合四边形顶点:结合棋盘边缘(Canny+霍夫直线)与楚河线,确定四个角点;
- 计算单应性矩阵:
cv2.getPerspectiveTransform(src_pts, dst_pts),其中dst_pts为标准棋盘角点(0,0; 800,0; 800,900; 0,900)。
核心代码:
# perspective_correct.py import cv2 import numpy as np def correct_perspective(img): # 1. HSV分割棋盘 hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) mask = cv2.inRange(hsv, (10,50,20), (20,255,120)) # 2. 检测楚河线(水平线) edges = cv2.Canny(mask, 50, 150) lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=100, minLineLength=200, maxLineGap=10) if lines is None: return img, None # 取y坐标中位数的两条线(楚河上下沿) y_coords = [line[0][1] for line in lines] + [line[0][3] for line in lines] y_mid = np.median(y_coords) chuhu_lines = [line for line in lines if abs((line[0][1]+line[0][3])/2 - y_mid) < 30] # 3. 拟合四边形顶点(简化版:取棋盘外框) contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contours: cnt = max(contours, key=cv2.contourArea) epsilon = 0.02 * cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, epsilon, True) if len(approx) == 4: src_pts = np.float32([point[0] for point in approx]) dst_pts = np.float32([[0,0], [800,0], [800,900], [0,900]]) M = cv2.getPerspectiveTransform(src_pts, dst_pts) warped = cv2.warpPerspective(img, M, (800,900)) return warped, M return img, None4.4 棋盘格子编号:将校正后坐标映射到“a1~i10”国际象棋式编号
标准中国象棋棋盘为9列(a~i)×10行(1~10),红方在下(行1~5),黑方在上(行6~10)。坐标映射公式:
- 列号:
col = chr(ord('a') + int(x / 80))(80=800px/9列); - 行号:
row = 10 - int(y / 90)(90=900px/10行,且y=0为顶部); - 最终格子编号:
f"{col}{row}"。
例如:校正后棋子中心(x=120, y=360)→col='b'(120/80=1.5→int=1→'a'+1='b'),row=10-360/90=10-4=6→"b6"(黑方第二列第六行)。
5. 避坑指南:象棋识别中5个血泪经验总结(现象→原因→解决)
5.1 现象:训练时val/mAP50突然暴跌(从0.62→0.21),但train/loss持续下降
原因:数据集中混入了3张“国际象棋”图像(误下载),其棋子形状(王冠、主教帽)与中文象棋“将/士”无任何相似性,导致val集分布偏移。YOLOv8的val/mAP计算基于全部14类,国际象棋样本使red_king等类在val中无法匹配,mAP被拉垮。
解决:用grep -r "international" chess_dataset/全局搜索,删除images/val/intl_chess_*.jpg及对应label;重新划分val集,确保100%为中文象棋。
5.2 现象:推理时大量“红车”被误判为“红马”,尤其在斜拍角度下
原因:“车”字繁体为“車”,上部“一”与“马”字上部“丶”在小尺寸下像素级相似;YOLOv8默认的iou=0.7过高,导致“車”字倾斜后IoU<0.7,触发NMS抑制,留下“马”框。
解决:降低NMS阈值至iou=0.5,并在后处理中添加形状过滤——计算检测框宽高比,车字框宽高比≈1.2(横排),马字≈0.8(竖排),剔除宽高比偏离>0.3的框。
5.3 现象:Ubuntu 20.04 CPU训练时,workers=8导致进程卡死,htop显示CPU占用100%但无进度
原因:Linux默认ulimit -n(单进程文件描述符上限)为1024,workers=8时每个worker需打开图像文件,总描述符超限。
解决:临时提升上限ulimit -n 65536,或永久修改/etc/security/limits.conf添加* soft nofile 65536。
5.4 现象:手机拍摄的低光照图中,“将”字边缘发灰,检测框偏大且置信度仅0.28,低于conf=0.3阈值被过滤
原因:YOLOv8默认数据增强中的brightness=0.2不足以模拟极端低光,模型未见过此类样本。
解决:在train.py中自定义增强,添加torchvision.transforms.ColorJitter(brightness=0.5, contrast=0.3),并将低光照图在训练集中的占比从10%提升至25%。
5.5 现象:部署到树莓派4B时,推理速度仅0.8 FPS,无法实时检测
原因:yolov8n_chess.pt为FP32权重,树莓派ARM CPU无FP16加速支持,且模型未量化。
解决:用torch.quantization.quantize_dynamic()对模型动态量化,生成yolov8n_chess_quantized.pt,速度提升至3.2 FPS(精度损失mAP50<0.02)。
6. 进阶技巧:用检测结果驱动棋局状态推演——从“看到棋子”到“理解走法”的轻量级规则引擎
6.1 棋子位置状态向量化:构建9×10棋盘矩阵与红黑阵营分离
检测结果经透视校正和格子编号后,需转化为结构化状态。本项目定义chess_state为三维张量:
state[0,:,:]:红方棋子矩阵(9列×10行),值为棋子ID(0=空,1=red_king,...,14=black_soldier);state[1,:,:]:黑方棋子矩阵(同上);state[2,:,:]:联合状态(0=空,正数=红方棋子ID,负数=黑方棋子ID绝对值)。
构建代码:
# state_engine.py import numpy as np def build_chess_state(detection_results, board_size=(9,10)): # detection_results: list of [cls_id, x, y, w, h] in corrected coord state_red = np.zeros(board_size, dtype=int) state_black = np.zeros(board_size, dtype=int) for cls_id, x, y, w, h in detection_results: col = int(x / 80) # 800px/9col row = 9 - int(y / 90) # 900px/10row, y=0 top → row=9 bottom if 0 <= col < 9 and 0 <= row < 10: if cls_id <= 7: # red pieces: 0~6 (red_king to red_soldier) state_red[col, row] = cls_id + 1 # ID from 1 else: # black pieces: 7~13 → map to 1~7 state_black[col, row] = cls_id - 6 # 7→1, 8→2, ..., 13→7 return np.stack([state_red, state_black], axis=0) # 示例:输入检测结果,输出state[2,9,1 <p> <a href="https://download.csdn.net/download/qq_39020934/92567836" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>