
简介这份资源是一套基于 Unity 引擎与 C# 语言、结合 Lean Touch 插件实现触摸屏物体识别桌的完整项目源码与工程文件面向需要开发互动式桌面、AR/VR 触控交互的 Unity 开发者。资源以 zip 压缩包形式提供共 3640 个文件包含 220 个 C# 脚本、106 个 dll 库、370 个 bin 资源、31 个场景文件、12 个 prefab 预设体以及 shader、材质、贴图等美术与配置资源包体大小约 24.54MB。目前已有 2442 人学习下载。通过该资源可以学习 Lean Touch 插件的导入与监听器设置、基于 Physics.Raycast 的物体拾取识别、触摸坐标与射线投射的转换逻辑、UI 交互处理及性能优化方法帮助快速搭建可复用的触摸屏物体识别桌系统。1. 触摸桌面不只是点按钮物体识别桌为什么绕不开 Unity 与 C#把手机、积木、卡片往桌上一放桌面就能“认出”放的是什么东西还能跟着手势拖拽、缩放、旋转——这套体验在展厅、互动教育、医疗康复和工业展项里出现得越来越频繁。做这类 Unity 触摸屏物体识别桌核心技术栈基本固定Unity 负责渲染和交互C# 负责业务逻辑与底层通信触摸屏提供多点触控输入物体识别则依赖视觉算法或 RFID 等物理标识。换句话说这不是一个“用 Unity 做个界面”的活而是要把触摸输入、识别结果、3D 场景三者实时串起来。我见过不少团队在这个标题上翻车有的把识别算法跑在服务端结果触摸屏上交互延迟高到没法用有的用了第三方识别 SDK却忽略了 Unity 的坐标系和触摸屏原生坐标系的转换导致物体放上去虚拟模型偏移半个桌面的距离。这篇文章我把一套能落地的方案拆开讲从触摸屏坐标怎么进 Unity到物体识别用视觉方案还是物理标识方案怎么选再到 C# 层的数据结构和性能优化最后是必踩的坑。目标是让你照着搭能在三天内跑出一个能演示、能交付的原型。2. 先把输入链路打通触摸屏坐标到 Unity 世界的三次变换2.1 为什么不能直接拿 Input.touchPosition 当桌面坐标Unity 里拿触摸点的标准做法是Input.GetTouch(0).position这个值是以屏幕像素为单位的 UI 坐标左下角为原点y 轴向上。但触摸屏物体识别桌的物理结构是摄像头或传感器在桌面下方或上方识别算法给出一组物体的像素坐标而触摸屏本身可能有独立的触控分辨率。两者如果不做统一就会出现“手指按在 A 点识别框却在 B 点”的错位。常见的触摸屏一体机触控分辨率和屏幕显示分辨率不一定一致。比如一块 43 寸的电容触摸屏显示分辨率是 1920x1080但触控 IC 上报的原始分辨率可能是 32767x32767系统驱动会做一次线性映射。Unity 拿到的Input.touchPosition已经是映射到显示分辨率之后的值但仍然是以屏幕左上角为原点的像素坐标。而物体识别算法比如 OpenCV 或深度学习模型输出的物体中心点通常是以图像左上角为原点、以图像像素尺寸为单位的坐标。如果摄像头画面分辨率是 1280x720而屏幕是 1920x1080直接从图像坐标映射到屏幕坐标x 和 y 都要乘一个比例系数而且 y 轴方向要翻转。我一般会在 C# 里写一个CoordinateMapper类专门处理这三套坐标摄像头图像坐标、屏幕像素坐标、Unity 世界坐标。不要在业务逻辑里到处做坐标换算否则后面调识别参数和触摸校准时会非常痛苦。2.2 一个可复用的坐标映射组件代码与参数说明using UnityEngine; /// summary /// 触摸屏物体识别桌坐标映射器 /// 处理摄像头图像坐标 - 屏幕像素坐标 - Unity世界坐标 的变换 /// /summary public class CoordinateMapper : MonoBehaviour { [Header(摄像头参数)] public Vector2 cameraResolution new Vector2(1280f, 720f); [Header(屏幕显示参数)] public Vector2 screenResolution new Vector2(1920f, 1080f); [Header(Unity桌面区域)] // 桌面在Unity世界中的矩形区域单位米 public Vector2 desktopWorldSize new Vector2(1.2f, 0.8f); public Vector2 desktopWorldCenter Vector2.zero; /// summary /// 将摄像头图像坐标左上角原点y向下转为屏幕像素坐标左下角原点y向上 /// /summary public Vector2 CameraImageToScreenPixel(Vector2 cameraPos) { float normalizedX cameraPos.x / cameraResolution.x; // 图像坐标y向下屏幕坐标y向上需要翻转 float normalizedY 1f - (cameraPos.y / cameraResolution.y); float screenX normalizedX * screenResolution.x; float screenY normalizedY * screenResolution.y; return new Vector2(screenX, screenY); } /// summary /// 将屏幕像素坐标转为Unity世界坐标 /// /summary public Vector2 ScreenPixelToWorld(Vector2 screenPos) { float normalizedX screenPos.x / screenResolution.x; float normalizedY screenPos.y / screenResolution.y; float worldX desktopWorldCenter.x (normalizedX - 0.5f) * desktopWorldSize.x; float worldY desktopWorldCenter.y (normalizedY - 0.5f) * desktopWorldSize.y; return new Vector2(worldX, worldY); } /// summary /// 摄像头图像坐标直接转Unity世界坐标一步到位 /// /summary public Vector2 CameraImageToWorld(Vector2 cameraPos) { Vector2 screenPixel CameraImageToScreenPixel(cameraPos); return ScreenPixelToWorld(screenPixel); } }这段代码解决的是“识别算法给的坐标”和“Unity 场景里物体位置”的关系。逻辑分两层先把摄像头图像坐标归一化到 0~1再映射到屏幕像素然后把屏幕像素归一化映射到 Unity 世界坐标。两个步骤分开写的好处是你可以随时检查是哪一层出了问题。比如触摸屏显示正常但物体位置偏移就要怀疑是cameraResolution和实际视频流分辨率不匹配如果连手指触摸都不准那要查的是触摸屏驱动校准而不是这段代码。参数设置上有两个容易被忽略的细节。第一cameraResolution必须填实际视频流的分辨率不是摄像头的最大分辨率。很多 USB 摄像头在 Unity 里通过WebCamTexture拿到的分辨率默认是 640x480而你设了 1280x720映射必然错位。第二desktopWorldSize和实际桌面物体放置区域的物理尺寸要对上。如果桌面上物体识别有效区域是 1.2 米 x 0.8 米就按这个填识别到的物体才能和真实桌面一一对应。2.3 多点触摸与物体拖拽识别框和手指的配合物体识别桌的交互不只是“放上去识别”更多的场景是“识别出来后用手指拖拽、旋转”。这里有一个关键问题识别算法给的是物体中心点和轮廓但手指触摸给的是触点。C# 里要把这两者关联起来我常用的做法是做一个TouchObjectBinder脚本每一帧检测是否有触摸点落在某个已识别物体的包围盒内如果有就把这个触摸点的 ID 和物体绑定直到该触摸点抬起。using System.Collections.Generic; using UnityEngine; public class TouchObjectBinder : MonoBehaviour { public float touchRadius 100f; // 屏幕像素单位触摸点命中物体的判定半径 private Dictionaryint, RecognizedObject _bindingMap new Dictionaryint, RecognizedObject(); void Update() { // 先处理新按下的触摸 for (int i 0; i Input.touchCount; i) { Touch touch Input.GetTouch(i); if (touch.phase TouchPhase.Began) { TryBindObject(touch.fingerId, touch.position); } else if (touch.phase TouchPhase.Ended || touch.phase TouchPhase.Canceled) { _bindingMap.Remove(touch.fingerId); } else if (touch.phase TouchPhase.Moved) { if (_bindingMap.ContainsKey(touch.fingerId)) { RecognizedObject obj _bindingMap[touch.fingerId]; obj.transform.position Camera.main.ScreenToWorldPoint( new Vector3(touch.position.x, touch.position.y, 10f)); } } } } private void TryBindObject(int fingerId, Vector2 touchPos) { // 遍历当前所有已识别物体找到距离最近且小于touchRadius的 RecognizedObject[] allObjects FindObjectsOfTypeRecognizedObject(); RecognizedObject nearest null; float minDist float.MaxValue; foreach (RecognizedObject obj in allObjects) { Vector3 screenPos Camera.main.WorldToScreenPoint(obj.transform.position); float dist Vector2.Distance(touchPos, screenPos); if (dist touchRadius dist minDist) { minDist dist; nearest obj; } } if (nearest ! null) { _bindingMap[fingerId] nearest; } } }这段代码的逻辑核心是fingerId的绑定关系。Unity 的多点触摸里每个触摸点从按下到抬起都有独立的fingerId用字典维护绑定关系能避免两根手指同时操作两个物体时互相干扰。touchRadius的取值要看屏幕尺寸和物体在屏幕上显示的大小43 寸屏、1080p 分辨率下我一般取 80~120 像素太小了用户不容易点中太大了会跟旁边物体误绑定。注意ScreenToWorldPoint的 z 值。桌面场景里的识别物体通常在 XY 平面上如果相机的视角是正交俯视z 传 10f 或任意正值都能正确换算因为正交相机不看深度如果用透视相机这个 z 值必须等于物体所在平面到相机的距离不然算出来的世界坐标会偏。这个坑我踩过在透视相机下传了 0 值物体全跑到相机位置附近去了。3. 物体识别方案选型视觉算法、RFID 还是两者的混搭3.1 纯视觉识别从 OpenCV 模板匹配到轻量级分类模型物体识别桌最“通用”的方案是纯视觉。摄像头架在桌面正上方俯拍整个桌面识别算法在图像里找到目标物体并输出类别和位置。对开发 Unity 的团队来说最常见的落地路径是 OpenCV 的模板匹配加轮廓检测适合识别形状规则、纹理清晰的物体比如积木块、数字卡片、动物卡片。模板匹配的原理很简单把每个要识别的物体拍一张模板图在摄像头实时画面里滑动匹配找到相似度最高的位置。OpenCV 的matchTemplate配合minMaxLoc能拿到匹配位置和分数。但这个方案的短板也很明显光照一变、物体稍微旋转、部分遮挡匹配分数立刻掉下来。所以做产品的团队一般不会只靠模板匹配而是用轮廓特征或轻量级分类模型。如果物体是印刷卡片我建议用 ArUco 码或 AprilTag 辅助识别。在卡片角落打印一个码识别算法先找码再根据码的 ID 和位姿推算卡片的类型和朝向。这样做同时解决了“识别是什么”和“识别在哪、朝向哪”两个问题。OpenCV 的aruco模块在 C# 里可以通过 OpenCVSharp 或 Unity 的 OpenCV for Unity 插件调用性能足够跑实时。识别到物体后需要把结果传给 C# 层。视觉算法如果跑在 PC 上可以直接在同一个进程里用 C# 调用 OpenCVSharp省去通信开销如果跑在独立的边缘设备或 Android 平板上就要走 TCP、UDP 或共享内存。我做过一个方案里摄像头在桌面上方算法跑在独立的 Mini PC 上通过 UDP 把“物体 ID 中心点像素坐标 旋转角度”广播给 Unity 客户端单包小于 100 字节延迟在 5ms 以内。3.2 RFID 与 Near-Field 方案的取舍什么时候别用视觉视觉方案不是万能的。有些物体外形极其相似只是内部数据不同比如不同药品的瓶子、不同批次的零件有些场景要求识别结果不受遮挡影响比如手盖住了物体的一半有些场景的光照条件不可控比如户外互动装置。这时候视觉方案的准确率和稳定性都会出问题RFID 就成了更务实的选择。RFID 识别桌的典型架构是桌面下方或边缘埋一圈 RFID 天线每个被识别的物体底部贴一个 RFID 标签标签里写入物体 ID。C# 通过串口或 USB 连接 RFID 读写器读写器上报标签 ID 和信号强度Unity 根据标签 ID 在场景里生成对应模型。这个方案的好处是识别几乎不受光照和遮挡影响误识别率极低坏处是你需要给每个实体物体贴标签而且 RFID 天线有感应区域物体要放在特定范围内才能读到。如果你做的是几十种物体以内的互动桌视觉方案更灵活因为实体物体不需要预埋任何东西如果物体种类超过一百种或者物体之间长得非常像RFID 的稳定性会让你少掉很多头发。还有一条中间路线视觉负责粗定位RFID 负责精确身份确认。比如摄像头判断物体的大致位置RFID 读数确认这个位置上的具体标签 ID两者在 C# 层做融合。3.3 算法进程与 Unity 的通信协议设计无论视觉还是 RFID识别数据进 Unity 都需要一个清晰的数据结构。C# 和算法进程之间的通信我推荐用 JSON over UDP。UDP 虽然不保证送达但识别桌场景在局域网内丢包率极低而且它的延迟比 TCP 的 Nagle 算法和重传机制稳定得多。你要是用 TCP万一算法进程卡了一下积压的数据包会让 Unity 端出现“物体位置瞬移”的诡异表现。using System; using System.Net; using System.Net.Sockets; using System.Text; using UnityEngine; [Serializable] public class RecognitionResult { public int objectId; public float x; // 归一化坐标 0~1左上角原点 public float y; // 归一化坐标 0~1左上角原点 public float rotation; // 度 public float confidence; // 0~1 } public class RecognitionUdpReceiver : MonoBehaviour { public int listenPort 29001; private UdpClient _udpClient; private RecognitionResult[] _latestResults new RecognitionResult[0]; void Start() { _udpClient new UdpClient(listenPort); _udpClient.BeginReceive(OnReceive, null); } private void OnReceive(IAsyncResult ar) { IPEndPoint remoteEndPoint new IPEndPoint(IPAddress.Any, 0); byte[] data _udpClient.EndReceive(ar, ref remoteEndPoint); string json Encoding.UTF8.GetString(data); try { _latestResults JsonHelper.FromJsonArrayRecognitionResult(json); } catch (Exception e) { Debug.LogWarning(识别结果解析失败: e.Message); } _udpClient.BeginReceive(OnReceive, null); } void Update() { // 在Unity主线程中消费_latestResults foreach (RecognitionResult result in _latestResults) { // 用CoordinateMapper转成世界坐标并更新对应物体 } } void OnDestroy() { _udpClient.Close(); } }这里有个重要的 C# 陷阱UdpClient.BeginReceive的回调是异步线程不能在里面直接操作 Unity 的 Transform 或 GameObject否则 Unity 会报UnityException: get_transform can only be called from the main thread。我的做法是回调里只解析数据并存入字段Update主线程里再消费。上面代码里的JsonHelper.FromJsonArray是因为 Unity 自带的JsonUtility不支持直接解析 JSON 数组需要包一层 wrapper 或者用第三方库如 Newtonsoft.Json。通信协议的数据字段里confidence这个参数很多团队会忽略。实际做产品时它不只是拿来显示“识别置信度”更关键的是用来做防抖如果连续几帧的confidence都低于阈值就判定物体已被移走或遮挡Unity 端隐藏对应的虚拟模型。阈值我一般设在 0.6 到 0.7 之间低于 0.6 的识别结果基本都是误检直接丢弃比展示出来更稳妥。4. C# 业务层架构从识别结果到桌面交互的粘合层怎么组织4.1 不要把所有逻辑堆在 MonoBehaviour 里很多从教程起步的开发者习惯把识别结果处理、物体生成、拖拽逻辑全写在一个脚本的Update里。在物体识别桌这种场景下物体数量可能有几十个加上触控、特效、音效单个脚本会膨胀到上千行调试和维护都很痛苦。用 C# 面向对象的方式拆一层会让整个项目清晰很多。我一般会拆成四层第一层是数据模型层定义RecognizedObject类包含物体 ID、显示名称、Prefab 引用、当前的识别置信度、绑定的触摸 ID 等。第二层是识别数据接入层就是前面写的 UDP 接收器只负责解析和缓存识别结果。第三层是对象管理服务层一个ObjectSpawnManager单例或可注入的服务类负责根据识别结果创建、更新、销毁场景里的物体。第四层是交互控制层处理触摸拖拽、旋转、缩放。using UnityEngine; public class RecognizedObject : MonoBehaviour { public int objectId; public string displayName; public float confidence; public bool isActive true; private Vector3 _targetPosition; private Quaternion _targetRotation; public void UpdateFromRecognition(Vector3 worldPos, float angle, float newConfidence) { _targetPosition worldPos; _targetRotation Quaternion.Euler(0f, 0f, angle); confidence newConfidence; } void Update() { // 平滑跟随识别结果避免位置跳变 transform.position Vector3.Lerp(transform.position, _targetPosition, 0.15f); transform.rotation Quaternion.Slerp(transform.rotation, _targetRotation, 0.15f); } }Vector3.Lerp的插值系数 0.15 是经验值。系数太小物体移动有拖影感太大则会显得生硬。识别算法的帧率在 15~30FPS 时0.10~0.20 之间都会有不错的效果如果算法在 60FPS 以上可以适当调到 0.3 左右。这个参数要留出来方便调整不同算法帧率下最优值差异挺大。这里的UpdateFromRecognition是给识别数据层调用的不直接改transform.position而是先存目标位置在Update里做平滑。为什么这么做因为识别算法输出的坐标是带抖动的物体静止时中心点也可能有正负几像素的波动直接赋值会导致虚拟模型在桌面上高频颤动。用 Lerp 做一个低通滤波视觉上会舒服得多。4.2 对象池物体反复出现和消失时防止 GC 卡顿识别桌的典型交互是用户拿走一个物体再放回来再拿走。每次识别结果里新增一个物体就去Instantiate消失就Destroy这在短时间内反复发生会让 Unity 的 GC垃圾回收压力很大。对象池是解决这个问题的标准手段。using System.Collections.Generic; using UnityEngine; public class ObjectPool : MonoBehaviour { public GameObject prefab; public int prewarmCount 10; private QueueGameObject _pool new QueueGameObject(); void Start() { for (int i 0; i prewarmCount; i) { GameObject obj Instantiate(prefab, transform); obj.SetActive(false); _pool.Enqueue(obj); } } public GameObject Get() { if (_pool.Count 0) { GameObject obj _pool.Dequeue(); obj.SetActive(true); return obj; } else { return Instantiate(prefab, transform); } } public void Return(GameObject obj) { obj.SetActive(false); _pool.Enqueue(obj); } }对象池的prewarmCount等于桌面上可能出现的最多物体数量。这个值要根据玩法定如果桌面上最多同时放 20 个识别物就预创建 20 个如果评估不了可以设一个比实际需求多 30% 的冗余值。池子的物体都挂在空节点ObjectPool下场景层级会干净很多。真正要注意的是从池子里取出的物体OnEnable时要做状态重置。比如一个物体曾经被旋转过角度、缩放过大重新放到桌面上时应该恢复默认。我习惯在RecognizedObject里加一个ResetState()方法在ObjectPool.Get()返回前调用。不然会出现回到桌上的物体悬空在之前被拖拽的位置、尺寸放大缩小不一致的怪象。4.3 识别生命周期管理出现、消失、持续存在三种状态识别桌和普通点击交互最大的不同是物体有“存在性”的概念。视觉算法并不是每帧都能稳定检测到所有物体可能某物体被手短暂遮挡了 0.5 秒算法没输出Unity 端就把模型隐藏了用户体验就是物体闪了一下消失又出现。这需要一套生命周期管理LostTimer机制来判断物体是真的被拿走还是暂时被遮挡或被误检遗漏。具体做法是为每个已识别的物体记录lastSeenTime。每帧更新时如果在新的识别结果里找到了这个物体就更新lastSeenTime并刷新状态如果没找到不立即隐藏而是等一个延迟时间比如 1.5 秒后再隐藏。超过延迟时间仍未出现才判定物体已离桌。这个延迟时间的设置要权衡设太短手晃动一下就闪设太长物体被拿走了一分多钟虚拟模型还挂在那儿很尴尬。室内互动场景 1.2~2 秒是安全区间。C# 实现时我习惯写一个RecognitionTracker类内部维护Dictionaryint, TrackedObjectState每个状态包含物体引用、lastSeenTime、pendingRemovalTime。每一帧按上面的规则更新状态到期未见的物体才回收到对象池。这套逻辑直接解决了多物体、低帧率识别场景下最常见的“幽灵物体”问题。5. 避坑排查识别桌项目里 4 个高频翻车现场与解决方案5.1 现象手指触摸和识别物体位置对不上越往边缘偏得越多这是识别桌项目里出现频率最高的问题。原因基本出在坐标映射的线性假设上。很多廉价电容触摸屏的边缘存在非线性畸变触摸 IC 给出的坐标在中心区域是准的越靠近边缘偏差越大。另外摄像头的镜头如果是广角镜头或鱼眼镜头画面边缘畸变更严重直接用线性映射当然会偏。解决分两步。第一步如果用的是广角镜头先在算法侧做去畸变处理OpenCV 里用cv2.undistort配合标定得到的畸变系数k1, k2, p1, p2。第二步在 Unity 侧做触摸校准让用户在屏幕上依次点几个已知位置的点记录触摸上报值和实际显示值的偏差用二次多项式拟合修正。这个校准文件可以序列化成 JSON 存到Application.persistentDataPath下每次启动时读取。不要用 PlayerPrefs 存校准数据量大而且结构复杂JSON 文件更合适。5.2 现象物体放上去反应慢识别结果要 1 秒以上才更新识别桌的“反应慢”往往不是摄像头的帧率问题而是识别算法本身的处理耗时。模板匹配在 1080p 画面下做全图搜索一次可能要 100~300ms再加上客户端的显示同步就感觉特别拖沓。解决思路是缩小识别区域而不是降低识别频率。因为物体只会出现在桌面范围内摄像头画面里桌面以外的区域对识别没有任何意义截掉反而能提升精度。另一个办法是降低摄像头实际处理的分辨率。物体识别不是拍照720p 甚至 540p 足够识别大尺寸的积木或卡片将图像缩小到原来的 1/4处理时间能降到 30ms 以内。我做过一个项目里识别算法原跑 1080p 需要 200ms降到 720p 后只需要 60ms准确率几乎没变化。5.3 现象Unity 里物体抖动厉害像得了帕金森抖动主要来自三方面识别算法输出坐标的随机波动、相机硬件噪声、以及代码里直接赋值。前面说的 Lerp 平滑能解决一部分但如果物体是静止的Lerp 只是让它在目标位置附近无限逼近仍然会有微小的残差抖动。我的做法是加一个死区阈值当目标位置和当前位置的距离小于0.005f米5 毫米时直接让物体停在当前位置不再做插值。这个阈值对应到物理桌面上是非常小的距离视觉上完全察觉不到但它能消除绝大多数静止状态的微抖。另外如果识别算法支持输出多帧平均值或卡尔曼滤波建议在算法侧就做不要再依赖 Unity 端弥补。我把低调放到算法侧把 Unity 端的 Lerp 保留作为最后的平滑兜底。5.4 现象物体识别正确但拖拽起来不跟手有滞后感识别桌的拖拽和 UI 按钮的拖拽体验要求不一样。UI 拖拽是即时响应的因为触点坐标就是手指位置但物体识别桌的拖拽是“手指碰到虚拟模型”然后将模型移动到触摸点。滞后的根源是触摸事件的处理顺序默认情况下物体模型的位置更新在Update里执行但触摸事件读取也在同一帧的同一阶段如果识别结果的更新把模型移到了别处触摸坐标和模型位置就产生了偏离。解决方法是把拖拽逻辑放到LateUpdate里执行。LateUpdate在所有Update执行完之后才跑此时所有识别结果和模型位置都已更新完毕触摸坐标再覆盖到模型位置上就不会产生帧内冲突。同时拖拽期间要暂停识别结果的坐标回写。我在RecognizedObject里加了一个isDragging标志拖拽中UpdateFromRecognition只更新 ID 和置信度不更新位置等手指抬起后恢复。这个细节不处理就会出现手指拖着模型模型还在被识别结果往回拽的拉锯感。6. 进阶落地技巧识别结果的置信度滤波与多桌联动扩展项目跑通基础流程以后再往下做就要考虑识别稳定性和系统架构的扩展。置信度滤波是最值得优先做的不要单帧判定而是用滑动窗口。我常用的做法是记录每个物体最近 5 帧的置信度只有当超过 3 帧的置信度大于阈值时才确认这个物体“有效”。这样能过滤掉摄像头画面里一闪而过的干扰物比如手影、反光点。同时把滤波后的置信度显示在 UI 上调试时能直观地看到算法在什么条件下会丢帧比对着日志猜高效得多。多桌联动是这类项目商业化常见的需求一个展厅里放三张识别桌每张桌上识别不同物体但内容要投射到同一块大屏或同一个 Unity 场景里。这时不要每张桌子单独跑一个 Unity 实例而是每张桌子的客户端只做输入采集和识别将结果汇总到一个主控 Unity 实例。通信协议可以沿用前面提到的 JSON over UDP只是在数据字段里加一个tableId。主控端按tableId分容器管理物体每张桌子的识别结果互不干扰。这样加一张桌子只需要配置新桌子的tableId和网络地址主控端代码一行都不用改。最后一个调试技巧我一定会集成到项目里在 Unity 场景中加一个 Debug 面板实时显示当前收到的识别结果数量、每帧置信度均值、坐标映射偏差值。这个面板在开发阶段默认开启交付时通过 PlayerPrefs 或启动参数关闭。依赖日志输出做调试在识别桌项目里非常低效因为问题往往出现在视觉上可感知但日志里不可见的地方——物体歪了 2 厘米、模型抖了一下、触摸晚了一帧。可视化调试面板能把这些瞬态问题变成可观察、可复现的现象省下的调试时间远超写面板的时间。做识别桌这几年我最大的体会是调试工具和识别算法本身一样重要。希望这套从坐标映射到生命周期管理再到排错的方法能让你少走几段我走过的弯路。本文还有配套的精品资源点击获取