1. 项目概述为视障者点亮数字世界的“眼睛”最近在捣鼓一个挺有意义的项目起因是身边一位视障朋友聊起他日常阅读纸质文档的困难。市面上虽然有一些文字转语音的App但要么需要手动拍照、对焦要么延迟高、体验割裂。我就琢磨能不能做一副更“无感”、更实时的智能眼镜让视障朋友戴上后眼前的世界能以声音的形式实时“翻译”出来。这个想法最终落地成了两个形态一个是主打便携与实时性的“智能眼镜”原型另一个是放在桌面上、功能更强大的“小恶魔桌面机”。核心目标就一个利用手头易得的开源硬件和成熟的技术栈打造一套低成本、高可用的辅助工具把视觉信息无缝转换成听觉信息。整个项目的核心逻辑并不复杂用摄像头捕捉图像通过算法识别出其中的文字再将文字转换成语音播放出来。但难点在于如何让这个过程足够快、足够准并且足够易用。这背后涉及硬件选型、软件架构、算法优化和用户体验设计等多个层面的考量。我选择了树莓派作为主控搭配摄像头模块用Python构建核心的图像处理与文字识别流程再通过蓝牙连接骨传导耳机或小型扬声器进行音频输出。而“小恶魔桌面机”则是在此基础上增加了更强大的计算单元比如用上了带NPU的开发板和固定角度的支架专攻文档阅读、药品说明书识别等桌面场景。这不仅仅是一个技术Demo它触及的是信息无障碍这个深层次的需求。对于视障人士而言获取印刷体文字信息是一道实实在在的屏障。我们习以为常的看菜单、读信件、查药品说明对他们来说可能都需要他人的协助。这个项目试图用技术的力量在一定程度上打破这道屏障赋予他们更独立的信息获取能力。接下来我会详细拆解从构思到实现的每一个环节包括踩过的坑和收获的经验希望能给想做类似项目的朋友一些切实的参考。2. 核心硬件选型与设计思路硬件是整个项目的基石选型直接决定了系统的性能上限、功耗、便携性和成本。我的设计思路很明确在保证核心功能实时图像捕捉、文字识别、语音合成流畅运行的前提下尽可能追求小型化、低功耗和模块化方便后续迭代和定制。2.1 主控单元为什么是树莓派4B/5主控芯片的选择上我几乎没有犹豫就圈定了树莓派。原因有几个首先是生态无敌。无论是摄像头驱动、蓝牙管理、GPIO控制还是系统层面的优化树莓派社区都有海量的教程和现成的解决方案能极大降低开发门槛。其次是性能足够。以树莓派4B为例其四核Cortex-A72处理器对于运行OpenCV进行图像预处理、调用Tesseract OCR引擎以及跑一个轻量级的TTS文本转语音库已经能提供可接受的性能。如果追求更强的AI推理能力树莓派5的算力提升或者搭配像Google Coral USB加速棒这样的外设可以尝试集成更先进的深度学习OCR模型。注意不要盲目追求最新型号。树莓派5性能虽强但功耗和发热也更高对电源的要求更严格。对于眼镜这类对体积和散热敏感的设备树莓派Zero 2 W或CM4计算模块是更紧凑的选择但需要一定的底板设计能力。我最初用4B做原型就是看中其接口齐全双HDMI、USB 3.0、调试方便。2.2 “眼睛”的选择摄像头模块的考量摄像头的选型决定了“输入”的质量。树莓派官方Camera Module 3是一个省心的选择它原生支持树莓派系统驱动完善通过CSI接口直接连接延迟低。其自动对焦功能对于不同距离的文字捕捉非常有用。如果追求更广的视角比如希望眼镜能覆盖更大视野范围可以考虑一些第三方的大广角CSI摄像头。另一个关键参数是帧率FPS和分辨率。对于实时文字识别我们并不需要很高的分辨率1080p甚至720p通常足够但需要一定的帧率来保证捕捉动态场景如手持镜头扫过文字时图像不模糊。在软件中我们通常会降低分辨率来换取处理速度。Camera Module 3在1080p下能达到50帧完全满足需求。实操心得在驱动安装上使用最新的Raspberry Pi OS原Raspbian系统通过sudo raspi-config启用摄像头接口是最稳妥的方式。曾经遇到过一个坑是在某些第三方精简版系统上摄像头驱动不完整导致OpenCV无法调用CSI摄像头最后换回官方系统解决。2.3 “嘴巴”与连接音频输出与蓝牙音频输出有两种主要方式有线音频和蓝牙音频。对于眼镜形态为了摆脱线缆束缚蓝牙是必然选择。我们需要一个低延迟的蓝牙音频模块与主控连接。树莓派本身集成了蓝牙但默认的音频协议如A2DP延迟可能高达200毫秒这对于需要音画同步的实时场景是不可接受的。解决方案是使用低延迟蓝牙编解码器如aptX LL或LDAC。但这需要蓝牙耳机也支持相应协议。一个更通用的软件方案是使用pulseaudio或pipewire配合ofono等工具将音频配置文件从A2DP切换到SCO模式常用于通话可以显著降低延迟虽然音质会有所下降但对于语音合成播放清晰度优先于保真度。对于“小恶魔桌面机”由于设备固定可以考虑使用USB声卡或HDMI音频输出连接一个有源音箱这样音质和稳定性更好无需担心蓝牙配对和续航问题。2.4 供电与结构设计智能眼镜的供电是个挑战。需要平衡电池容量、体积和重量。我选用了一块3.7V、2000mAh的锂电池配合一块小型的5V升压板给树莓派供电。实测下来树莓派4B在高负载下功耗可达3A以上所以电池续航大约在1-2小时这提醒我们软件层面做功耗优化如动态调整CPU频率、空闲时休眠摄像头的必要性。结构设计上我用3D打印制作了眼镜框和部件固定壳。将树莓派和电池仓设计在镜腿后端以平衡配重摄像头位于鼻梁上方。散热也是重点尤其是在封闭的壳体内需要在关键芯片位置设计通风孔甚至考虑添加微型散热片或风扇。“小恶魔桌面机”则简单很多一个亚克力或3D打印的盒子就能容纳树莓派、电源和一个小型扬声器摄像头通过可调节的云台固定在盒子顶部指向桌面。3. 软件架构与核心算法实现软件部分是这个项目的灵魂目标是构建一个高效、稳定的流水线图像采集 - 预处理 - 文字检测与识别 - 文本后处理 - 语音合成 - 音频播放。我主要使用Python来搭建整个流程因为它拥有丰富的库支持和快速的开发迭代能力。3.1 开发环境搭建与关键依赖操作系统选择Raspberry Pi OS Lite无桌面版以节省资源。首先是一些基础配置和依赖安装# 更新系统并安装核心工具 sudo apt update sudo apt upgrade -y sudo apt install -y python3-pip python3-venv git cmake # 安装OpenCV的系统依赖和本体这是一个耗时较长的过程 sudo apt install -y libopencv-dev python3-opencv # 安装Tesseract OCR引擎及其语言包 sudo apt install -y tesseract-ocr sudo apt install -y tesseract-ocr-chi-sim # 简体中文语言包如果需识别中文 # 安装文本转语音引擎这里选择离线、轻量的espeak或flite sudo apt install -y espeak flite # 创建Python虚拟环境并安装PyPI包 python3 -m venv ~/venv_assist source ~/venv_assist/bin/activate pip install pip --upgrade pip install pytesseract pillow numpy pyaudio避坑指南直接pip install opencv-python在树莓派上可能会编译失败或版本不兼容。优先使用系统包管理器apt安装python3-opencv虽然版本可能稍旧但稳定性有保障。如果必须使用最新版OpenCV需要从源码编译过程非常漫长数小时。3.2 图像采集与预处理流水线使用OpenCV的VideoCapture接口捕获摄像头画面。为了降低延迟我们直接从CSI摄像头读取数据并设置合适的分辨率和帧率。import cv2 # 对于树莓派CSI摄像头使用GStreamer管道或直接cv2.VideoCapture(0)均可 # 这里使用一个较高效的GStreamer管道适用于树莓派4 def create_csi_pipeline(sensor_id0, width1280, height720, framerate30): return ( flibcamerasrc camera{sensor_id} ! fvideo/x-raw, width{width}, height{height}, framerate{framerate}/1 ! fvideoconvert ! appsink ) cap cv2.VideoCapture(create_csi_pipeline(), cv2.CAP_GSTREAMER) if not cap.isOpened(): # 备用方案尝试普通V4L2驱动 cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) # 预处理函数 def preprocess_image(frame): # 1. 转换为灰度图减少计算量 gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 2. 降噪 denoised cv2.GaussianBlur(gray, (5, 5), 0) # 3. 提高对比度使用CLAHE自适应直方图均衡化 clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8,8)) enhanced clahe.apply(denoised) # 4. 二值化根据光照情况选择阈值或自适应阈值 # _, binary cv2.threshold(enhanced, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) binary cv2.adaptiveThreshold(enhanced, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2) return binary预处理的目标是突出文字区域抑制背景干扰。在实际场景中光照不均、纸张褶皱、复杂背景都是挑战。自适应阈值化adaptiveThreshold比全局阈值化threshold在光照变化场景下鲁棒性更好。3.3 文字识别OCR引擎的选择与优化我测试了两种方案传统的Tesseract OCR和基于深度学习的PaddleOCR。方案一Tesseract PyTesseractTesseract是开源OCR的标杆准确度不错对打印体识别效果好且资源消耗相对较低。import pytesseract from PIL import Image def ocr_with_tesseract(preprocessed_image): # 将OpenCV图像转换为PIL图像 pil_img Image.fromarray(preprocessed_image) # 配置Tesseract参数提高识别精度 custom_config r--oem 3 --psm 6 -l engchi_sim # OEM 3代表默认LSTM引擎PSM 6假定为统一区块文本语言为中英混合 text pytesseract.image_to_string(pil_img, configcustom_config) return text.strip()优点部署简单占用资源少速度较快。缺点对不规则排版、倾斜、复杂背景或手写体识别能力较弱。方案二PaddleOCR百度开源的PaddleOCR提供了更先进的深度学习模型识别准确率特别是对中文和复杂场景的识别通常优于Tesseract。from paddleocr import PaddleOCR # 初始化模型首次运行会自动下载模型模型较大 ocr_engine PaddleOCR(use_angle_clsTrue, langch, use_gpuFalse) # 树莓派上use_gpu设为False def ocr_with_paddleocr(frame): # 这里输入原始BGR图像或预处理后的图像均可 result ocr_engine.ocr(frame, clsTrue) text_blocks [] if result is not None: for line in result: if line and line[1]: # line[1]是识别结果和置信度 text_blocks.append(line[1][0]) # 提取文本 return \n.join(text_blocks)优点识别精度高对复杂场景适应性强。缺点模型体积大几百MB加载慢推理速度在树莓派上较慢即使使用量化后的轻量模型对内存和CPU要求高。实操心得对于“智能眼镜”这种需要实时性的移动场景我最终选择了Tesseract并通过精心预处理和参数调优来提升其准确率。而对于“小恶魔桌面机”由于对实时性要求稍低用户将文档放在摄像头下但对手写、复杂排版文档识别要求高我使用了PaddleOCR的轻量版模型并配合缓存机制识别过的区域短时间内不再重复识别来平衡速度和精度。3.4 文本后处理与语音合成TTSOCR识别出的文本通常包含杂讯、换行符混乱、标点错误等问题需要后处理。import re def post_process_text(raw_text): if not raw_text: return # 1. 去除多余的空格和换行 text .join(raw_text.split()) # 2. 合并因换行被断开的单词简单规则针对英文 # 更复杂的需要基于词典或NLP模型 # 3. 纠正一些常见的OCR错误可建立简单的替换表 correction_map { 0: O, # 数字0和字母O的混淆 1: I, 5: S, } for wrong, right in correction_map.items(): text text.replace(wrong, right) # 4. 中文文本处理合并因空格分开的词语如果OCR输出带了空格 # text text.replace( , ) # 谨慎使用可能误伤英文 return text语音合成我选择了离线的pyttsx3库它后端调用系统自带的espeak或flite无需网络延迟极低。import pyttsx3 class TTSPlayer: def __init__(self): self.engine pyttsx3.init() # 配置语音参数 self.engine.setProperty(rate, 150) # 语速 self.engine.setProperty(volume, 0.9) # 音量 voices self.engine.getProperty(voices) # 选择一个听起来更自然的声音如果有 for voice in voices: if english in voice.name.lower(): self.engine.setProperty(voice, voice.id) break def speak(self, text): if text: # 使用非阻塞方式播放避免阻塞主循环 self.engine.say(text) self.engine.runAndWait() # 注意runAndWait是阻塞的。对于实时流需要更复杂的队列和线程管理。对于需要更自然语音的场景可以考虑在线TTS API如Google TTS、Azure TTS但会引入网络延迟和依赖。3.5 主程序流程与线程管理实时性要求我们必须妥善处理图像采集、处理和音频播放这三个耗时操作避免界面卡顿。我采用了多线程架构主线程负责图像采集和显示如果需要调试界面。处理线程从主线程获取最新的图像帧进行预处理、OCR和后处理。音频线程接收处理线程产生的文本调用TTS引擎播放。线程间通过线程安全的队列queue.Queue传递数据。一个关键细节是图像帧的传递要使用拷贝避免处理线程修改时主线程仍在使用的帧数据。import threading import queue import time class RealTimeOCRSystem: def __init__(self): self.frame_queue queue.Queue(maxsize2) # 缓存最近2帧防止堆积 self.text_queue queue.Queue() self.is_running True def capture_thread(self): cap cv2.VideoCapture(create_csi_pipeline(), cv2.CAP_GSTREAMER) while self.is_running: ret, frame cap.read() if ret: if self.frame_queue.full(): try: self.frame_queue.get_nowait() # 丢弃旧帧 except queue.Empty: pass self.frame_queue.put(frame.copy()) # 必须拷贝 time.sleep(0.03) # 控制采集频率 def process_thread(self): while self.is_running: try: frame self.frame_queue.get(timeout1) processed preprocess_image(frame) text ocr_with_tesseract(processed) clean_text post_process_text(text) if clean_text: # 只有识别到有效文本才放入队列 self.text_queue.put(clean_text) except queue.Empty: continue def audio_thread(self): tts TTSPlayer() last_text while self.is_running: try: text self.text_queue.get(timeout1) # 简单的去重如果新文本和刚读过的文本高度相似则跳过 if text ! last_text and len(text) 3: # 避免朗读过短的杂讯 tts.speak(text) last_text text except queue.Empty: continue def run(self): threads [] threads.append(threading.Thread(targetself.capture_thread)) threads.append(threading.Thread(targetself.process_thread)) threads.append(threading.Thread(targetself.audio_thread)) for t in threads: t.start() # 主线程可以在这里做其他事比如监听退出信号 try: while True: time.sleep(0.1) except KeyboardInterrupt: self.is_running False for t in threads: t.join()4. 蓝牙音频低延迟配置与系统优化对于智能眼镜蓝牙音频的延迟是影响体验的关键。默认的A2DP协议延迟在200ms以上会导致声音明显滞后于视觉变化。4.1 配置树莓派蓝牙为低延迟模式我们可以通过修改PulseAudio的配置强制蓝牙使用SCOSynchronous Connection-Oriented模式这是用于通话的低延迟协议。首先安装必要的蓝牙工具和库sudo apt install -y pulseaudio pulseaudio-module-bluetooth bluez bluez-tools sudo usermod -a -G lp pi # 将用户加入lp组通常pi用户已在此组然后创建一个PulseAudio的配置文件来优先使用SCOsudo nano /etc/pulse/default.pa在文件末尾添加# 设置蓝牙音频为低延迟 load-module module-bluetooth-policy load-module module-bluetooth-discover load-module module-switch-on-connect # 以下参数尝试优化延迟 set-default-sink alsa_output.bluez_sink set-sink-volume alsa_output.bluez_sink 65536 # 最大音量更直接的方法是使用bluetoothctl命令行工具在连接蓝牙耳机后设置其配置为headset_head_unitHSP/HFP而非a2dp_sink。HSP/HFP协议延迟更低。bluetoothctl # 进入交互模式后 connect XX:XX:XX:XX:XX:XX # 你的耳机MAC地址 # 连接成功后在另一个终端查看设备信息获取其路径如/org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX # 然后设置配置文件 menu profile select-profile headset_head_unit back exit注意切换到HSP/HFP模式后音频质量会显著下降单声道带宽窄但延迟可降至50ms左右对于语音播放勉强可接受。这不是一个完美的方案。如果蓝牙耳机和发射端都支持aptX Low Latency或类似技术延迟可以做到40ms以下且音质无损但这需要硬件支持。4.2 系统级性能优化为了让树莓派更专注于我们的应用需要进行一些系统优化超频与散热在/boot/config.txt中谨慎超频并确保散热良好。over_voltage2 arm_freq1750 gpu_freq600关闭不必要的服务禁用图形桌面如果使用Lite版则无需此步、蓝牙发现、Wi-Fi节能等。sudo systemctl disable avahi-daemon bluetooth hciuart sudo systemctl stop avahi-daemon # 设置Wi-Fi为高性能模式 sudo iwconfig wlan0 power off调整进程优先级让我们的Python主进程获得更高的CPU调度优先级。import os os.nice(-10) # 降低nice值提高优先级需要sudo权限运行使用PyPy解释器对于计算密集型的Python代码如图像处理使用PyPy代替CPython可以获得显著的性能提升有时可达数倍。但需注意PyPy对某些C扩展库如OpenCV的兼容性可能需要使用CFFI版本或重新编译。5. “小恶魔桌面机”的增强功能桌面机形态解放了功耗和体积限制因此可以集成更强大的功能。5.1 文档区域自动检测与跟踪对于固定在桌面的摄像头我们可以假设文档在一个相对稳定的区域。使用OpenCV的轮廓检测来自动定位文档。def find_document_contour(frame): gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) blurred cv2.GaussianBlur(gray, (5, 5), 0) edged cv2.Canny(blurred, 30, 150) # Canny边缘检测 contours, _ cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 假设最大的轮廓是文档 if contours: largest_contour max(contours, keycv2.contourArea) peri cv2.arcLength(largest_contour, True) approx cv2.approxPolyDP(largest_contour, 0.02 * peri, True) # 如果近似为四边形则认为是文档 if len(approx) 4: return approx return None找到文档轮廓后可以进行透视变换将倾斜的文档“拉正”这能极大提升OCR识别率。5.2 语音交互与控制为方便用户操作可以集成简单的语音唤醒和命令识别。使用Snowboy离线或Porcupine离线作为唤醒词引擎用SpeechRecognition库配合在线API如Google Speech或离线的Vosk进行简单的命令识别。import speech_recognition as sr def listen_for_command(): r sr.Recognizer() with sr.Microphone() as source: print(请说指令...) audio r.listen(source, timeout3, phrase_time_limit2) try: # 使用离线引擎Vosk需额外安装模型 # 或使用在线引擎需网络 command r.recognize_google(audio, languagezh-CN) print(f识别到指令{command}) return command.lower() except sr.UnknownValueError: print(无法理解音频) return None except sr.RequestError: print(语音服务错误) return None通过命令如“开始扫描”、“暂停”、“读这里”来控制桌面机的行为。5.3 结果缓存与历史记录桌面机可以保存识别过的文本片段甚至拍照存档方便用户回听或导出。设计一个简单的SQLite数据库来管理这些记录。import sqlite3 from datetime import datetime def init_database(): conn sqlite3.connect(scan_history.db) c conn.cursor() c.execute(CREATE TABLE IF NOT EXISTS scans (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT, image_path TEXT, recognized_text TEXT)) conn.commit() conn.close() def save_scan(text, image_frameNone): timestamp datetime.now().isoformat() image_filename fscan_{timestamp}.jpg if image_frame is not None else None if image_filename: cv2.imwrite(fscans/{image_filename}, image_frame) conn sqlite3.connect(scan_history.db) c conn.cursor() c.execute(INSERT INTO scans (timestamp, image_path, recognized_text) VALUES (?, ?, ?), (timestamp, image_filename, text)) conn.commit() conn.close()6. 常见问题与调试心得实录在开发过程中我遇到了无数问题这里记录下最典型的一些及其解决方案。6.1 摄像头无法打开或图像扭曲问题cv2.VideoCapture(0)返回False或图像颜色奇怪、条纹。排查首先用libcamera-hello或raspistill命令测试摄像头硬件是否正常。检查sudo raspi-config中摄像头接口是否已启用。对于CSI摄像头尝试使用GStreamer管道如前文所示而非V4L2驱动。OpenCV的V4L2后端对树莓派CSI摄像头支持可能不佳。图像扭曲或颜色问题可能是通道顺序BGR vs RGB或编码格式问题。在imshow前尝试转换颜色空间cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)。6.2 OCR识别率低下问题识别出的文本错漏百出。解决预处理是关键90%的OCR问题源于图像质量。尝试不同的预处理组合调整对比度、使用不同的二值化方法全局/自适应、尝试形态学操作开运算、闭运算去除噪点。调整Tesseract参数--psm页面分割模式参数至关重要。对于单行文字尝试--psm 7对于单列文本尝试--psm 4对于稀疏文本尝试--psm 11。使用tesseract --help-psm查看所有选项。提供ROI感兴趣区域如果知道文字大概位置先用轮廓检测或滑动窗口定位文字区域然后只对该区域进行OCR能减少干扰。语言包确保安装了正确的语言包如tesseract-ocr-chi-sim并在配置中指定-l engchi_sim。6.3 系统延迟过高音频卡顿问题从看到文字到听到语音延迟超过1秒或音频播放不连贯。优化降低图像分辨率将摄像头采集分辨率从1080p降至720p或480p能极大减轻处理负担。减少处理频率不必对每一帧都进行OCR。可以每处理3帧跳过2帧或者只有当图像变化超过一定阈值如通过帧间差分时才触发OCR。优化代码避免在循环中重复初始化模型如Tesseract或PaddleOCR引擎。使用profile或cProfile模块找出性能瓶颈。检查蓝牙延迟如前所述尝试切换蓝牙音频模式。如果可能使用有线音频输出对比测试以确定延迟是否来自音频链路。6.4 蓝牙连接不稳定或无法发现设备问题树莓派搜不到蓝牙耳机或连接频繁断开。排查确保蓝牙服务运行sudo systemctl status bluetooth。解除旧配对使用bluetoothctl移除已配对的旧设备重新扫描和配对。检查电源管理有些USB蓝牙适配器或树莓派内置蓝牙会因为省电模式关闭。编辑/etc/bluetooth/main.conf将AutoEnable设为true并尝试在/etc/rc.local中添加rfkill unblock bluetooth。干扰问题将树莓派与USB 3.0设备、Wi-Fi路由器等潜在干扰源保持距离。6.5 Python环境依赖冲突问题安装某个库如OpenCV、PyAudio时失败或运行时出现ImportError。解决始终坚持使用虚拟环境这是隔离项目依赖的最佳实践。优先使用apt安装系统级依赖对于OpenCV、Tesseract等先用apt安装python3-opencv,tesseract-ocr然后再用pip安装其Python封装如pytesseract。注意版本匹配。对于复杂的C扩展库如果pip安装失败考虑从源码编译。这通常需要安装大量的开发工具和库build-essential,cmake,libgtk2.0-dev等过程繁琐但一劳永逸。利用预编译的wheel对于树莓派可以搜索piwheels仓库这是一个为ARM架构预编译的Python包仓库能加速安装并避免编译错误。在pip install时可以临时指定索引pip install --extra-index-url https://www.piwheels.org/simple some-package。这个项目从构思到实现是一个典型的软硬件结合的系统工程。它没有用到什么高深莫测的黑科技更多的是对现有开源工具链的整合、调优和问题解决。最大的收获不是做出了一个多酷的原型而是在解决一个个具体问题比如如何把蓝牙延迟降低100毫秒如何让OCR在光线不佳时也能工作的过程中对底层技术有了更真切的理解。对于想入门嵌入式AI或辅助技术开发的朋友这类项目是一个绝佳的起点目标明确技术栈丰富且充满人文关怀。最后所有的代码和3D打印模型文件我都整理放在了GitHub上欢迎交流指正。