CV -- YOLOv8 图像分割(GPU环境)
目录
参考视频:
标注
JSON转为TXT
训练
验证
参考视频:
使用 Yolov8 自定义数据集进行图像分割_哔哩哔哩_bilibili
标注
数据集:
我使用的是一些苹果数据集,可以在我的csdn资源中下载:
https://download.csdn.net/download/2403_83182682/90405543?spm=1001.2014.3001.5503
这里标注使用的 labelme 标注软件,下载和使用都很简单,下载需要打开 cmd 控制台,输入下载代码:
pip install labelme
labelme
输入 labelme 打开软件页面:
中文版的自己玩玩就会了,常用按钮如下
点击【打开目录】,输入苹果文件夹,点击【创建多边形】,绘制多边形,点击保存,然后继续绘制下一张
标记完成后是这样的
需要将 jpg 文件和 json文件分别放入文件夹中,方便后面数据转换。
JSON转为TXT
通过 LabelMe 工具绘制多边形标注后生成的 JSON 文件是一种结构化的数据文件,它遵循了一定的格式来存储图像标注信息。但是 yolov8 官方规定需要的是标注文件,即 .txt 文件,我这里提供一段 json 转 txt 文件的 Python 代码:
# -*- coding: utf-8 -*-
from tqdm import tqdm
import shutil
import random
import os
import argparse
from collections import Counter
import yaml
import jsondef mkdir(path):if not os.path.exists(path):os.makedirs(path)def convert_label_json(json_dir, save_dir, classes):json_paths = os.listdir(json_dir)classes = classes.split(',')mkdir(save_dir)for json_path in tqdm(json_paths):# for json_path in json_paths:path = os.path.join(json_dir, json_path)with open(path, 'r') as load_f:json_dict = json.load(load_f)h, w = json_dict['imageHeight'], json_dict['imageWidth']# save txt pathtxt_path = os.path.join(save_dir, json_path.replace('json', 'txt'))txt_file = open(txt_path, 'w')for shape_dict in json_dict['shapes']:label = shape_dict['label']label_index = classes.index(label)points = shape_dict['points']points_nor_list = []for point in points:points_nor_list.append(point[0] / w)points_nor_list.append(point[1] / h)points_nor_list = list(map(lambda x: str(x), points_nor_list))points_nor_str = ' '.join(points_nor_list)label_str = str(label_index) + ' ' + points_nor_str + '\n'txt_file.writelines(label_str)def get_classes(json_dir):'''统计路径下 JSON 文件里的各类别标签数量'''names = []json_files = [os.path.join(json_dir, f) for f in os.listdir(json_dir) if f.endswith('.json')]for json_path in json_files:with open(json_path, 'r') as f:data = json.load(f)for shape in data['shapes']:name = shape['label']names.append(name)result = Counter(names)return resultdef main(image_dir, json_dir, txt_dir, save_dir):# 创建文件夹mkdir(save_dir)images_dir = os.path.join(save_dir, 'images')labels_dir = os.path.join(save_dir, 'labels')img_train_path = os.path.join(images_dir, 'train')img_val_path = os.path.join(images_dir, 'val')label_train_path = os.path.join(labels_dir, 'train')label_val_path = os.path.join(labels_dir, 'val')mkdir(images_dir)mkdir(labels_dir)mkdir(img_train_path)mkdir(img_val_path)mkdir(label_train_path)mkdir(label_val_path)# 数据集划分比例,训练集75%,验证集15%,测试集15%,按需修改train_percent = 0.90val_percent = 0.10total_txt = os.listdir(txt_dir)num_txt = len(total_txt)list_all_txt = range(num_txt) # 范围 range(0, num)num_train = int(num_txt * train_percent)num_val = int(num_txt * val_percent)train = random.sample(list_all_txt, num_train)# 在全部数据集中取出trainval = [i for i in list_all_txt if not i in train]# 再从val_test取出num_val个元素,val_test剩下的元素就是test# val = random.sample(list_all_txt, num_val)print("训练集数目:{}, 验证集数目:{}".format(len(train), len(val)))for i in list_all_txt:name = total_txt[i][:-4]srcImage = os.path.join(image_dir, name + '.jpg')srcLabel = os.path.join(txt_dir, name + '.txt')if i in train:dst_train_Image = os.path.join(img_train_path, name + '.jpg')dst_train_Label = os.path.join(label_train_path, name + '.txt')shutil.copyfile(srcImage, dst_train_Image)shutil.copyfile(srcLabel, dst_train_Label)elif i in val:dst_val_Image = os.path.join(img_val_path, name + '.jpg')dst_val_Label = os.path.join(label_val_path, name + '.txt')shutil.copyfile(srcImage, dst_val_Image)shutil.copyfile(srcLabel, dst_val_Label)obj_classes = get_classes(json_dir)classes = list(obj_classes.keys())# 编写yaml文件classes_txt = {i: classes[i] for i in range(len(classes))} # 标签类别data = {'path': os.path.join(os.getcwd(), save_dir),'train': "images/train",'val': "images/val",'names': classes_txt,'nc': len(classes)}with open(save_dir + '/segment.yaml', 'w', encoding="utf-8") as file:yaml.dump(data, file, allow_unicode=True)print("标签:", dict(obj_classes))if __name__ == "__main__":"""python json2txt_nomalize.py --json-dir my_datasets/color_rings/jsons --save-dir my_datasets/color_rings/txts --classes "cat,dogs""""classes_list = 'apple' # 类名parser = argparse.ArgumentParser(description='json convert to txt params')parser.add_argument('--image-dir', type=str, default='D:\OneDrive\桌面\yolov8-segment\datasets\segment\images', help='图片地址')parser.add_argument('--json-dir', type=str, default='D:\OneDrive\桌面\yolov8-segment\datasets\segment\json', help='json地址')parser.add_argument('--txt-dir', type=str, default='D:\OneDrive\桌面\yolov8-segment\datasets\segment\\txt', help='保存txt文件地址')parser.add_argument('--save-dir', default='D:\OneDrive\桌面\yolov8-segment\datasets\segment\seg', type=str, help='保存最终分割好的数据集地址')parser.add_argument('--classes', type=str, default=classes_list, help='classes')args = parser.parse_args()json_dir = args.json_dirtxt_dir = args.txt_dirimage_dir = args.image_dirsave_dir = args.save_dirclasses = args.classes# json格式转txt格式convert_label_json(json_dir, txt_dir, classes)# 划分数据集,生成yaml训练文件main(image_dir, json_dir, txt_dir, save_dir)
第 90 行左右可以修改数据集划分比例,默认是 90% 训练集,10%验证集。
第147到150行依次为图片地址、json文件地址、保存txt文件地址、分割好的数据集地址
我的项目创建目录如下(都是可以自己修改的):
运行代码
运行成功,训练集是19张图片,验证集是3张,检测到的标签总数量为 53。
运行成功后会帮您创建 Yolov8 训练所需的文件格式,并且将 txt文件放入正确的位置:
训练
训练环境使用的GPU,需要配置的可以看我前文:
CV -- 基于GPU版显卡CUDA环境+Pycharm YOLOv8 检测-CSDN博客
训练代码如下:
from torch.cuda import devicefrom ultralytics import YOLOmodel = YOLO('D:\OneDrive\桌面\yolov8-segment\weights\yolov8n-seg.pt')model.train(data='D:\OneDrive\桌面\yolov8-segment\datasets\segment\seg\segment.yaml',epochs=300, #训练轮次imgsz=640, #输入图片尺寸(会转换为该尺寸)batch=4, #每次训练的批量device='cuda:0', #使用GPU训练workers=0 #windows GPU训练需加上该参数,否则会报错)
print("训练结束!")
这里使用的是预训练模型,yolov8n-seg.pt,大家可以在我的资源中获取到:
https://download.csdn.net/download/2403_83182682/90405472?spm=1001.2014.3001.5503
训练结束后会生成一些图表:
输出文件说明:
F1-置信度曲线 (BoxF1_curve.png)
观察方法: F1分数是模型准确度的度量,结合了精确度和召回率。在这个图表中,您应该寻找F1分数最高的点,该点对应的置信度阈值通常是模型最佳的工作点。
精确度-置信度曲线 (BoxP_curve.png)
观察方法: 精确度代表了模型预测为正类的样本中实际为正类的比例。在该曲线中,应关注随置信度增加,精确度如何提高,以及在哪个置信度水平上精确度开始下降,这有助于确定阈值设定。
精确度-召回率曲线 (BoxPR_curve.png)
观察方法: 该曲线展示了精确度与召回率之间的权衡。理想的模型应在高精确度和高召回率处达到平衡。通常查看曲线下面积来评估模型整体性能。
召回率-置信度曲线 (BoxR_curve.png)
观察方法: 召回率是指所有正类中模型正确预测的比例。这个图表中,召回率通常随着置信度阈值的降低而增加。理想的置信度阈值通常是召回率较高,但置信度不过低的点。
混淆矩阵 (confusion_matrix.png)
观察方法: 查看矩阵的对角线,对角线上的数值越高表示分类结果越准确。同时观察非对角线元素,了解哪些类别容易被误分类。
标准化混淆矩阵 (confusion_matrix_normalized.png)
观察方法: 与非标准化混淆矩阵类似,但通过标准化可以更容易地比较不同类别之间的分类准确率,特别是在类别样本量不均匀的情况下。
标签分布 (labels.jpg)
观察方法: 柱状图部分显示了每个类别的实例数量,有助于了解数据集中各类别的分布情况。散点图部分可以显示样本的位置分布,有助于了解样本在输入空间的分布特性。
标签相关图 (labels_correlogram.jpg)
观察方法: 相关图显示了数据标签之间的相关性,深色的格子表示较高的正相关,浅色表示较低的相关或负相关。这有助于了解不同类别之间的关系。
掩膜F1-置信度曲线 (MaskF1_curve.png)
观察方法: 类似于F1-置信度曲线,但特别用于评估模型在像素级分类或分割任务中的性能。寻找曲线中F1得分最高的点来确定最佳的置信度阈值。
精确度-召回率曲线 (Precision-Recall Curve) (MaskPR_curve.png)
如何观察理解: 此图表展示了在不同召回率水平上模型精确度的变化。蓝色的线表示所有类别的平均精确度。曲线下的面积(AUC)越大,模型性能越好。理想状态是曲线靠近右上角,即高召回率和高精确度。
召回率-置信度曲线 (Recall-Confidence Curve) (MaskR_curve.png)
如何观察理解: 该图标展示了模型在不同置信度阈值下召回率的变化。您应关注在召回率保持高的同时,置信度阈值的选择。最佳操作点通常是召回率开始显著下降之前的置信度值。
训练和验证指标图 (results.png)
如何观察理解: 这张图显示了多个指标的训练和验证过程,其中包括损失函数的变化和性能指标如精确度和mAP。下降的损失和上升的性能指标通常表明模型在学习过程中正在改进。平滑的曲线有助于识别趋势。
损失和性能指标图
如何观察理解: 类似于上一个图表,这个可能包含了不同的损失和性能指标。每个小图标展示了训练过程中的具体方面,如框体损失、分割损失、分类损失等。这有助于诊断模型在哪些方面表现良好,在哪些方面可能需要进一步优化。
Weights文件:这是一个模型权重文件,通常以.pt(PyTorch模型)格式保存。它包含了经过训练的神经网络的所有参数和权重。这个文件是模型训练过程的直接产物,用于后续的图像识别和分析任务。
Args.yaml文件:这个文件通常包含了模型训练时使用的配置参数。它详细记录了训练过程中使用的所有设置,如学习率、批大小、训练轮数等。这个文件的目的是为了提供一个清晰的训练配置概览,使得训练过程可以被复现或调整。
左半部分是损失函数图,损失函数下降,可能并不能说明训练结果很好,但如果损失函数上升,那训练结果一定不好,说明你的数据可能出现了很大的错误,或者一些其他不好的事情正在发生。
通过对测试数据的详细分析,我们可以观察到原始标注与模型预测的掩膜之间存在差异很小,这实际上体现了模型具备出色的语义分割能力。具体而言,尽管在某些局部细节上可能存在细微偏差,但模型整体能够准确捕捉并区分不同对象的边界和区域,展示了其在复杂场景下对图像内容的强大理解能力。这种高水平的分割精度不仅验证了模型训练的有效性,还为其在实际应用中的性能提供了有力保障。
验证
可以使用我们训练好的模型,对苹果图片进行图像分割,训练好的模型的权重文件中有两个模型文件,第一个是最好的模型,第二个是最后一次训练的模型,一般使用第一个模型。
训练代码:
from ultralytics import YOLO
import cv2
import numpy as np# 模型路径和图像路径
model_path = 'D:/OneDrive/桌面/yolov8-segment/runs/segment/train8/weights/best.pt'
image_path = 'D:/OneDrive/桌面/apple2.jpg'# 加载模型并进行预测
model = YOLO(model_path,task='segment')
results = model.predict(source=image_path,save=True,show=True)
成功运行后就能得到分割图片:
可以看见分割效果是有的,但也有点瑕疵,把杯子也识别成 apple 了,可能是因为数据集太少或者标注问题。
当然,也可使用代码将各个 apple 的掩膜提取出来:
from ultralytics import YOLO
import cv2
import numpy as np# 模型路径和图像路径
model_path = 'D:/OneDrive/桌面/yolov8-segment/runs/segment/train8/weights/best.pt'
image_path = 'D:/OneDrive/桌面/apple2.jpg'# 加载模型并进行预测
model = YOLO(model_path,task='segment')
results = model.predict(source=image_path,save=True,show=True)# 读取图像
img = cv2.imread(image_path)
H, W, _ = img.shape
print(img.shape)# # 遍历每个结果中的掩码
for i, result in enumerate(results):for j, mask in enumerate(result.masks.data):# 将mask从GPU移动到CPU,并转换为numpy数组mask = mask.cpu().numpy()# 如果mask是多维的,选择第一个通道(假设单通道)if len(mask.shape) > 2:mask = mask[0]# 归一化到0-255范围,并转换为uint8类型mask = (mask * 255).astype(np.uint8)# 调整大小以匹配原图尺寸mask_resized = cv2.resize(mask, (W, H))# 保存掩码图像output_path = f'./mask_{i}_{j}.png'cv2.imwrite(output_path, mask_resized)print(f"Saved {output_path}")
得到各个掩膜的 png 文件:
这些掩膜文件在某些时候非常有用。
感谢您的三连!!!
相关文章:

CV -- YOLOv8 图像分割(GPU环境)
目录 参考视频: 标注 JSON转为TXT 训练 验证 参考视频: 使用 Yolov8 自定义数据集进行图像分割_哔哩哔哩_bilibili 标注 数据集: 我使用的是一些苹果数据集,可以在我的csdn资源中下载: https://download.csdn.net/do…...

Cherry-Studio下载安装教程,AI面向开发者的工具或平台(付安装包)
文章目录 一、Cherry Studio是什么?二、功能特点 一、Cherry Studio是什么? Cherry Studio 是一款开源跨平台的多模型服务桌面客户端,集成超 300 个大语言模型,内置 300 多个预配置 AI 助手,支持多格式文件处理、全局…...
【Javascript Day19】BOM
目录 BOM对象的方法 定时器方法 短信验证码案例 计时器元素动画 同步代码和异步代码 location对象 跳转查询页面参数 跳转多查询参数 BOM对象的方法 // window.alert("提示");// window 中提供的方法和属性,可以在省略window对象的情况下直接调用…...
git 操作 已经 commit 但是没有 push 怎么办
前言: 在操作commit后发现提交错了分支,直接切换分支是不行的,只能先取消commit的代码才能切换分支,因此记录一下git的操作 如果你已经执行了 git commit 但还没有进行 git push,可以通过以下几种方式撤回或修改提交…...

在 macOS 的 ARM 架构上按住 Command (⌘) + Shift + .(点)。这将暂时显示隐藏文件和文件夹。
在 macOS 的 ARM 架构(如 M1/M2 系列的 Mac)上,设置 Finder(访达)来显示隐藏文件夹的步骤如下: 使用快捷键临时显示隐藏文件: 在Finder中按住 Command (⌘) Shift .(点ÿ…...
【核心算法篇二十】《DeepSeek符号回归:让AI化身「数学神探」破解数据背后的宇宙公式》
“宇宙最不可理解之处,就是它居然可以被理解。”——爱因斯坦 如果让AI来续写这句话,或许会是:"数据最迷人的地方,在于它总能用数学公式讲出故事。"今天我们要聊的DeepSeek符号回归技术,就是教AI从杂乱数据中自动发现精妙数学规律的「黑魔法」。全程高能预警,建…...

如何在 Visual Studio Code 中使用 DeepSeek R1 和 Cline?
让我们面对现实吧:像 GitHub Copilot 这样的 AI 编码助手非常棒,但它们的订阅费用可能会在你的钱包里烧一个洞。进入 DeepSeek R1 — 一个免费的开源语言模型,在推理和编码任务方面可与 GPT-4 和 Claude 3.5 相媲美。将它与 Cline 配对&#…...

PHP旅游门票预订系统小程序源码
🌍 旅游门票预订系统:一站式畅游新体验,开启您的梦幻旅程 🌟 一款基于ThinkPHPUniapp精心雕琢的旅游门票预订系统,正翘首以待,为您揭开便捷、高效、全面的旅游预订新篇章!它超越了传统预订平台…...

在项目中调用本地Deepseek(接入本地Deepseek)
前言 之前发表的文章已经讲了如何本地部署Deepseek模型,并且如何给Deepseek模型投喂数据、搭建本地知识库,但大部分人不知道怎么应用,让自己的项目接入AI模型。 文末有彩蛋哦!!! 要接入本地部署的deepsee…...
notepad++右键菜单不见了
卸载时没点击完成,又重新安装了一个,最终导致了一些bug,导致右键没有notepad菜单。 解决方式: 新建一个register.reg文件,加入以下代码,然后双击执行即可 代码说明:Open with Notepad 是右…...
如何用ollama快速布署deepseek-r1大模型
deepseek在春节期间因为特朗普的一番发言而在中国已几乎人尽皆知,热度到了连90高寿的老父亲都向我推荐这个中国产的AI大模型,而且它是开源的!我试验了下,用ollama也可以快速度安装布署deepseek-r1大模型。本想写篇文章来介绍下dee…...

python-leetcode 36.二叉树的最大深度
题目: 给定一个二叉树root,返回其最大深度 二叉树的最大深度是指从根节点到最远叶子节点的最长路径上的节点数 方法一:深度优先搜索 知道了左子树和右子树的最大深度l和r,那么该二叉树的最大深度即为:max(l,r)1 而左子树和右子树的最大深…...

MySQL事务的特性和隔离级别
一、事务的特性 事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作,即这些操作要么同时成功,要么同时失败 事务的有以下四个特性(acid)…...

Oracle视图(基本使用)
视图 视图是通过定制的方式显示一个或者多个表的数据。 视图可以视为“虚拟表”或“存储的查询”。 视图的优点: 提供了另外一种级别的表安全性隐藏了数据的复杂性简化了用户的SQL命令隔离基表结构的改变通过重命名列,从另一个角度提供数据。 视图里…...

C++ Primer 类的作用域
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...

【学习笔记】Cadence电子设计全流程(二)原理图库的创建与设计(上)
【学习笔记】Cadence电子设计全流程(二)原理图库的创建与设计(上) 2.1 OrCAD X Capture 界面预览2.2 原理图元件符号的组成2.3 原理图库的创建和元件的创建2.4 以 STM32F103T8U6 芯片为例创建元件 全部内容见专栏:【Ca…...

学习数据结构(11)二叉树(堆)下
1.堆的概念 如果有⼀个集合 K {k0,k1,k2,...,k(n-1)} ,把它的所有元素按完全二叉树的形式存储在一个一维数组中,并满足:K(i)<2*i1且K(i)<2*i2(K(i)>2*i1且K(i)>2*i2&a…...

HarmonyOS NEXT网络状态监听HTTP和RCP请求网络
当我们在HarmonyOS NEXT中开发的应用,基本上都会使用网络请求,从服务端获取数据在客户端显示或者供用户交互,有时候网络发生变化时,我们需要做一些相应的操作,接下来我们一起来了解下在HarmonyOS NEXT下如何监听网络状…...

MySQL数据库(4)—— 数据类型
目录 一,数据类型分类 二,数值类型 2.1 tinyint类型 2.2 bit类型 2.3 float类型 2.4 decimal类型 三,字符串类型 3.1 char类型 3.2 varchar类型 四,时间日期类型 五,enum和set类型 5.1 基本使用 5.2 解释查…...

如何在Odoo 18中创建记录规则Rule
如何在Odoo 18中创建记录规则Rule 记录规则是管理访问控制的关键,它能让你依据用户角色,定义谁可以在系统内查看、创建或修改特定记录。例如,公司中的普通员工只能查看或修改与与自己直接相关的数据,而经理则有权限访问和编辑所有…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...

【Post-process】【VBA】ETABS VBA FrameObj.GetNameList and write to EXCEL
ETABS API实战:导出框架元素数据到Excel 在结构工程师的日常工作中,经常需要从ETABS模型中提取框架元素信息进行后续分析。手动复制粘贴不仅耗时,还容易出错。今天我们来用简单的VBA代码实现自动化导出。 🎯 我们要实现什么? 一键点击,就能将ETABS中所有框架元素的基…...

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践
在 Kubernetes 集群中,如何在保障应用高可用的同时有效地管理资源,一直是运维人员和开发者关注的重点。随着微服务架构的普及,集群内各个服务的负载波动日趋明显,传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...

Unity VR/MR开发-VR开发与传统3D开发的差异
视频讲解链接:【XR马斯维】VR/MR开发与传统3D开发的差异【UnityVR/MR开发教程--入门】_哔哩哔哩_bilibili...

DL00871-基于深度学习YOLOv11的盲人障碍物目标检测含完整数据集
基于深度学习YOLOv11的盲人障碍物目标检测:开启盲人出行新纪元 在全球范围内,盲人及视觉障碍者的出行问题一直是社会关注的重点。尽管技术不断进步,许多城市的无障碍设施依然未能满足盲人出行的实际需求。尤其是在复杂的城市环境中ÿ…...