前言
增强现实(AR)技术正以前所未有的速度重塑我们与现实世界的交互方式,而工业、物流、医疗等专业领域对高效、无接触信息获取的需求尤为迫切。作为国内领先的AR硬件提供商,Rokid AR眼镜凭借其轻量化设计、卓越的显示效果和强大的交互能力,已成为众多企业实现智能化升级的关键工具。其中,快速、精准的扫码识别功能,作为连接物理世界与数字信息的核心桥梁,在资产追踪、设备点检、仓库管理、生产流程控制等场景中扮演着至关重要的角色。
为了助力开发者更高效地将这一核心能力集成到基于Unity引擎构建的Rokid AR应用中,我们特别整理并开源了这份Unity3D C# 实现的Rokid AR眼镜扫码识别功能解决方案。本资源不仅提供了清晰的技术实现思路,更包含了可直接集成、修改的完整C#源代码。
效果
关注并私信 RokidAR QRScan 免费获取体验安装包(底部公众号)。
实现过程
核心实现原理,是抓取眼镜端的摄像头画面,先通过CameraPreview
类,在Unity中获取到眼镜摄像头的预览画面,再通过ZXing插件的解析出码的内容,识别到特定的内容做特定的处理逻辑。
UI搭建
基础的是一个预览画面+识别结果和扫描按钮:
开发
这里就不用自己启动摄像头截取画面,而是采用SDK内自带的CameraPreview
类,它主要用于在Unity中实现摄像头预览功能。它依赖于设备原生接口(通过NativeInterface.NativeAPI
)来获取摄像头数据,并将其实时显示在UI的RawImage组件上。通过一下代码可以获取画面(Texture2D):
CameraPreview.instance.previewTex;
完整的CameraPreview代码如下:
using UnityEngine;
using UnityEngine.UI;
using Rokid.UXR.Native;
using Rokid.UXR.Utility;
namespace Rokid.UXR.Demo
{
public class CameraPreview : MonoBehaviour
{
private bool isInit;
public Texture2D previewTex;
public RawImage rawImage;
private int width, height;
public static CameraPreview instance = null;
public void Init()
{
width = NativeInterface.NativeAPI.GetPreviewWidth();
height = NativeInterface.NativeAPI.GetPreviewHeight();
if (NativeInterface.NativeAPI.GetGlassName().Equals("Rokid Max Plus"))
{
NativeInterface.NativeAPI.SetCameraPreviewDataType(3);
previewTex = new Texture2D(width, height, TextureFormat.RGBA32, false);
}
else
{
NativeInterface.NativeAPI.SetCameraPreviewDataType(1);
previewTex = new Texture2D(width, height, TextureFormat.BGRA32, false);
}
NativeInterface.NativeAPI.OnCameraDataUpdate += OnCameraDataUpdate;
isInit = true;
}
private void OnCameraDataUpdate(int width, int height, byte[] data, long timestamp)
{
Loom.QueueOnMainThread(() =>
{
previewTex.LoadRawTextureData(data);
previewTex.Apply();
rawImage.texture = previewTex;
});
}
private void Awake()
{
#if !UNITY_EDITOR
rawImage.color = Color.white;
#endif
if (instance == null)
instance = this;
NativeInterface.NativeAPI.StartCameraPreview();
}
public void Release()
{
if (isInit)
{
NativeInterface.NativeAPI.OnCameraDataUpdate -= OnCameraDataUpdate;
NativeInterface.NativeAPI.StopCameraPreview();
NativeInterface.NativeAPI.ClearCameraDataUpdate();
isInit = false;
}
}
private void OnDestroy()
{
Release();
}
private void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
{
Release();
}
else
{
NativeInterface.NativeAPI.StartCameraPreview();
}
}
private void Update()
{
if (isInit == false && NativeInterface.NativeAPI.IsPreviewing())
{
Init();
}
}
}
}
扫描启动代码:
if (cor != null) return;
barcodeReader = new BarcodeReader {
Options = new DecodingOptions { TryHarder = true },
AutoRotate = true
};
isScan = true;
ScanObj?.SetActive(true);
cor = StartCoroutine(ScanQRCode());
这里的启动有防止重复启动处理,初始化ZXing解码器(启用增强识别和自动旋转),并启动识别协程。
二维码识别核心的处理如下:
try {
Texture2D previewTex = CameraPreview.instance.previewTex;
Color32[] pixels = previewTex.GetPixels32();
Result result = barcodeReader.Decode(
pixels,
previewTex.width,
previewTex.height
);
if (result != null) {
resultText.text = "扫描结果: " + result.Text;
if (result.Text == "特定内容") {
resultText.text = "识别结果: " + result.Text;
StopScan();
yield break;
}
}
} catch (System.Exception ex) {
Debug.LogError(ex.Message);
}
从CameraPreview(外部摄像头管理模块)获取一帧图像,解码流程:获取纹理像素数据(GetPixels32())和调用ZXing的Decode()方法解析二维码。
停止识别扫描:
public void StopScan() {
if (cor != null) StopCoroutine(cor);
ScanObj?.SetActive(false); // 隐藏扫描UI
isScan = false;
cor = null;
}
其他尝试
上面的开发实现过程是最终的版本,不过在开发过程中,踩过了很多的坑,这里也分享出来。
刚开始时,是打算直接启动摄像头来通过webCamTexture截取画面像素进识别:
void FixedUpdate()
{
if (isScan && webCamTexture.isPlaying && webCamTexture.didUpdateThisFrame)
{
// 获取摄像头图像数据
Color32[] color32 = webCamTexture.GetPixels32();
var result = barcodeReader.Decode(color32, webCamTexture.width, webCamTexture.height);
if (result != null)
{
// Debug.Log("识别结果: " + result.Text);
resultText.text = "识别结果: " + result.Text;
// 在这里处理识别结果,例如触发事件或更新UI
//webCamTexture.Stop(); // 可选:识别成功后停止摄像头
}
}
}
但运行起来效果不理想,而且画面有卡顿问题。
还尝试了异步GPU读取、多线程解码、帧同步和主线程调度的处理:
IEnumerator ScanQRCode()
{
while (true)
{
yield return new WaitForEndOfFrame();
if (CameraPreview.instance?.previewTex == null) continue;
System.Threading.ManualResetEvent threadSignal = null;
System.Exception threadException = null;
bool success = false;
try
{
var pixelArray = CameraPreview.instance.previewTex.GetPixels32();
int width = CameraPreview.instance.previewTex.width;
int height = CameraPreview.instance.previewTex.height;
threadSignal = new System.Threading.ManualResetEvent(false);
threadException = null;
success = false;
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
Result result = barcodeReader.Decode(pixelArray, width, height);
if (result?.Text == "rokidar.shaxiguzheng.com")
{
success = true;
}
}
catch (System.Exception ex)
{
threadException = ex;
}
finally
{
threadSignal.Set();
}
});
}
catch (System.Exception ex)
{
Debug.LogError(ex);
continue;
}
if (threadSignal == null) continue;
while (!threadSignal.WaitOne(0))
{
yield return null;
}
try
{
if (threadException != null)
{
throw threadException;
}
if (success)
{
UnityMainThreadDispatcher.Instance().Enqueue(() =>
{
StopScan();
});
yield break;
}
}
catch (System.Exception ex)
{
Debug.LogError(ex);
}
}
}
不过最终因为插件依赖和平台兼容问题,没有采用此方法。
源码
https://download.csdn.net/download/qq_33789001/91579018
关注并私信 RokidAR QRScan 免费获取体验安装包(底部公众号)。