Unity调用OpenCV微信二维码识别库扫码

Unity有个项目使用ZXing.Net扫二维码时发现在昏暗环境下识别效果并不好,达不到微信扫一扫那种效果,于是选用了微信在OpenCV开源的二维码识别库(WeChatQRCode库),效果确实提升了不少。

Demo下载


关键步骤:

1.下载导入OpenCV for Unity插件 (可找陶某)

2.下载 “4个模型文件” 放StreamingAssets文件夹

3.实现模型文件拷贝到 “持久化文件夹”

由于我的项目是安卓端,构造WeChatQRCode对象需要提供这4个模型文件路径,一开始是直接提供StreamingAssets路径,奈何在安卓端无法识别,于是就在代码中就将4个模型文件拷贝到持久化文件夹路径下,再提供之后路径即可构造WeChatQRCode对象。下面是相关代码:

using System.Collections;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.Networking;

public class QRCodeFilePath : MonoBehaviour
{
    private string[] fourFileName = new string[4] { "detect.prototxt", "detect.caffemodel", "sr.prototxt", "sr.caffemodel" };



    public static string[] fourFilePath { get; set; } = new string[4];
    public static bool isLoaded { get; set; } = false;










    private IEnumerator Start()
    {
        yield return null;
        //判断是否已经存在4个文件
        bool isHaveFiles = fourFileName.All((item) =>
        {
            return File.Exists(Application.persistentDataPath + "/" + item);
        });
        if (isHaveFiles)
        {
            fourFilePath = fourFileName.Select((item) => Application.persistentDataPath + "/" + item).ToArray();
            isLoaded = true;
            yield break;
        }
        //不存在,就将StreamingAssets下的4个文件拷贝到 持久化文件夹
        for (int i = 0; i < fourFileName.Length; i++)
        {
            yield return CopyStePathFileToPerPath(fourFileName[i]);
        }
        isLoaded = true;
    }
    //实现一个将StreamingAssets文件夹下的某文件 拷贝到持久化文件夹下
    private IEnumerator CopyStePathFileToPerPath(string fileName)
    {
        UnityWebRequest request = UnityWebRequest.Get(Application.streamingAssetsPath + "/" + fileName);
        yield return request.SendWebRequest();
        if (request.result == UnityWebRequest.Result.Success)
        {
            File.WriteAllBytes(Application.persistentDataPath + "/" + fileName, request.downloadHandler.data);
        }
    }
}

4.扫码线程对象实现

在Unity主线程解析二维码容易造成卡顿,需要另开一个线程。

using OpenCVForUnity.CoreModule;
using OpenCVForUnity.Wechat_qrcodeModule;
using System.Collections.Generic;
using System.Threading;

public static class QRScaner
{
    private static Thread thread = null;//扫码线程(主线程扫码会造成卡顿)
    private static WeChatQRCode weChatQRCode;//主对象


    public static Mat mat;//传入要扫描的图片(mat)

    public static List<string> rStrs = new List<string>();//返回的结果(多码支持)
    public static List<Mat> rPoints = new List<Mat>();//返回的二维码的图像位置(多码支持)







    public static void Start()
    {
        thread = new Thread(ThreadFun);
        thread.Start();
    }

    public static void Stop()
    {
        if (thread != null)
        {
            thread.Abort();
        }
    }


    private static void ThreadFun()
    {
        while (true)
        {
            if (mat != null)
            {
                if (weChatQRCode != null)
                {
                    rStrs = weChatQRCode.detectAndDecode(mat, rPoints);
                }
                else
                {
                    if (QRCodeFilePath.isLoaded)
                    {
                        var fourPath = QRCodeFilePath.fourFilePath;
                        weChatQRCode = new WeChatQRCode(fourPath[0], fourPath[1], fourPath[2], fourPath[3]);
                    }
                }
                mat = null;
            }
        }
    }
}

5.扫码测试代码

using OpenCVForUnity.CoreModule;
using OpenCVForUnity.UnityUtils;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;

public class WebCamMgr : MonoBehaviour
{
    [SerializeField] RawImage rawImage;
    private WebCamTexture camTexture;//相机贴图
    private float checkQRTime = 1f;//每隔1秒检测一次二维码
    private float checkQRDeltaTime;//检测二维码累计时间
    private Mat mat;//检测的图像
    private bool isFindQCode = true;//是否检测二维码
    [SerializeField] GameObject pointPrefab;//显示二维码所在点位的预制体

    #region 
    [SerializeField] GameObject showInfoPanel;//显示二维码扫描信息的Panel
    private Text showInfoText;//显示信息的Text
    #endregion



    


    void Start()
    {
        #region 启动相机
        var camName = "";
#if UNITY_EDITOR
        camName = WebCamTexture.devices[0].name;
#else
    camName = WebCamTexture.devices.FirstOrDefault((e) => e.isFrontFacing == false).name;//后摄像头
#endif
        camTexture = new WebCamTexture(camName, 1920, 1080);
        camTexture.Play();
        rawImage.texture = camTexture;
        (rawImage.transform as RectTransform).sizeDelta = new Vector2(camTexture.requestedWidth, camTexture.requestedHeight);
#if !UNITY_EDITOR
        rawImage.transform.Rotate(Vector3.forward, -90);//由于安卓相机输出的是横向矩形贴图?,需旋转为纵向矩形贴图
#endif
        #endregion

        #region 启动二维码扫描线程
        mat = new Mat(this.camTexture.height, this.camTexture.width, CvType.CV_8UC4);
        QRScaner.Start();
        #endregion

        #region 显示扫描信息的面板
        showInfoText = showInfoPanel.GetComponentInChildren<Text>(true);
        //关闭按钮
        showInfoPanel.GetComponentInChildren<Button>(true).onClick.AddListener(() =>
        {
            showInfoPanel.SetActive(false);
            //摧毁所有点位
            while (rawImage.transform.childCount != 0)
            {
                DestroyImmediate(rawImage.transform.GetChild(0).gameObject);
            }
            //相机播放
            this.camTexture.Play();
            //继续扫码
            isFindQCode = true;
        });
        #endregion
    }

    private void Update()
    {
        if (!isFindQCode)
        {
            return;
        }

        checkQRDeltaTime += Time.deltaTime;
        if (checkQRDeltaTime > checkQRTime)
        {
            checkQRDeltaTime = 0;
            Utils.webCamTextureToMat(camTexture, mat);
            QRScaner.mat = mat;
        }

        if (QRScaner.rStrs != null && QRScaner.rStrs.Count != 0)
        {
            isFindQCode = false;
            camTexture.Pause();

            List<string> rStrs = QRScaner.rStrs;//内容列表
            List<Mat> rPoints = QRScaner.rPoints;//二维码点位列表

            QRScaner.rStrs = null;
            //显示二维码的位置
            for (int i = 0; i < rStrs.Count; i++)
            {
                GameObject tempObj = Instantiate(pointPrefab, rawImage.transform);
                tempObj.transform.localPosition = ConvertToCenterOrigin(camTexture.width, camTexture.height, GetMatV2(rPoints[i]));
            }


            //等待一小段时间,显示扫描结果面板
            IEnumerator WaitShowPanel()
            {
                yield return new WaitForSeconds(.5f);
                showInfoPanel.SetActive(true);
                StringBuilder stringBuilder = new StringBuilder("扫描结果:");
                foreach (var item in rStrs)
                {
                    stringBuilder.AppendLine(item);
                }
                showInfoText.text = stringBuilder.ToString();
                showInfoPanel.SetActive(true);
            }
            StartCoroutine(WaitShowPanel());
        }
    }



    //二维码Mat转换为二维坐标
    public Vector2 GetMatV2(Mat mat)
    {
        Vector2 point0 = new Vector2((int)mat.get(0, 0)[0], (int)mat.get(0, 1)[0]);
        Vector2 point1 = new Vector2((int)mat.get(1, 0)[0], (int)mat.get(1, 1)[0]);
        Vector2 point2 = new Vector2((int)mat.get(2, 0)[0], (int)mat.get(2, 1)[0]);
        Vector2 point3 = new Vector2((int)mat.get(3, 0)[0], (int)mat.get(3, 1)[0]);

        float centerX = (int)((point0.x + point1.x + point2.x + point3.x) / 4.0f);
        float centerY = (int)((point0.y + point1.y + point2.y + point3.y) / 4.0f);
        return new Vector2(centerX, centerY);
    }
    /// <summary>
    /// 将基于矩形左下角为原点的坐标转换为以矩形中心为原点的坐标
    /// </summary>
    /// <param name="rectWidth">矩形的宽度</param>
    /// <param name="rectHeight">矩形的高度</param>
    /// <param name="point">基于左下角为原点的Vector2坐标</param>
    /// <returns>基于矩形中心为原点的Vector2坐标</returns>
    public Vector2 ConvertToCenterOrigin(float rectWidth, float rectHeight, Vector2 point)
    {
        float x = point.x;
        float y = point.y;
        // 计算矩形中心点相对于左下角的偏移量
        float centerXOffset = rectWidth / 2;
        float centerYOffset = rectHeight / 2;
        // 将基于左下角的坐标转换为基于中心的坐标
        float newX = x - centerXOffset;
        float newY = centerYOffset - y;
        return new Vector2(newX, newY);
    }

}

二维码提示点位由于线程时间不同步导致有些偏移,如果你有好的优化方法还请多指教

猜你喜欢

转载自blog.csdn.net/njiyue/article/details/140420075
今日推荐