• unity 手写板 截取游戏画面 识别手写文字 全家桶


    要求离线识别,不能接外网
    识别手写文字分为3步

    1. 第一步,做手写板
    2. 第二步,截取游戏画面
    3. 第三步,识别

    第一步,做个手写板
    我们可以使用LineRender实现手写板功能
    具体思想为,读取鼠标位置,当鼠标的位移超过阈值时,为LineRender添加一个新点,听起来不靠谱,但实际上效果还不错

    public class DrawLine : MonoBehaviour
    {
        /// 
        /// 线条的父物体
        /// 
        private Transform lineParent;
        /// 
        /// 线的材质 修改这个材质的颜色可以更改线的颜色
        /// 
        private Material lineMaterial;
        /// 
        /// 画笔的宽度
        /// 
        public float paintSize = 3;
        /// 
        /// 如果鼠标按下
        /// 
        public bool isMouseDown = false;
        /// 
        /// 当前的线条绘制器
        /// 
        private LineRenderer currentLine;
        /// 
        /// 当前的鼠标位置
        /// 
        private Vector3 currentMousePostion;
        /// 
        /// 上一个鼠标的位置
        /// 
        private Vector3 lastMosuePostion;
        /// 
        /// 当前笔记的所有点
        /// 
        private List<Vector3> points = new List<Vector3>();
    
        private new void Awake()
        {
            base.Awake();
            lineParent = GameObject.Find("LineParent").transform;
            lineMaterial = Resources.Load<Material>("Material/LineMaterial");
        }
        private void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                GameObject newLine = new GameObject();
                newLine.transform.parent = lineParent;
                currentLine = newLine.AddComponent<LineRenderer>();
                currentLine.materials[0] = lineMaterial;
                currentLine.startWidth = paintSize;
                currentLine.endWidth = paintSize;
                isMouseDown = true;            
            }
    
            if (isMouseDown)
            {
                //当前的鼠标的位置
                currentMousePostion = Input.mousePosition;
                currentMousePostion = new Vector3(currentMousePostion.x, currentMousePostion.y, 10);
                //超出阈值,添加一个新的点,阈值自己定
                if (Vector3.Distance(currentMousePostion, lastMosuePostion) >= 1f)
                {
                    AddLinePoint(currentMousePostion);
                    lastMosuePostion = currentMousePostion;
                }
            }
    
            if (Input.GetMouseButtonUp(0))
            {
                isMouseDown = false;
                currentLine = null;
                lastMosuePostion = Vector3.zero;
                points.Clear();
            }
        }
    
        /// 
        /// 添加线的点
        /// 
        private void AddLinePoint(Vector3 pos)
        {
            points.Add(pos);
            currentLine.positionCount = points.Count;
            currentLine.SetPositions(points.ToArray());
        }
        /// 
        /// 清理已经画好的点
        /// 
        public void ClearLine()
        {
            for (int i = 0; i < lineParent.childCount; i++)
            {
                Destroy(lineParent.GetChild(i).gameObject);
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97

    Unity中的相关物体:所有的线条都是这个物体的子物体
    在这里苏偶有的图片描述
    注意,我是用的分辨率是1920*1080,使用屏幕坐标画出的线非常巨大,每个像素都是1米,所以我将主相机放在x960y540的的位置,如果你觉得太大,可以使用射线检测一个背景Plane,用射线击中点,代替屏幕坐标,可以有效降低线的尺寸
    效果:
    请添加图片描述


    第二步 截取游戏画面
    有三种不同的截图方式可供我们选择
    1 使用ScreenCapture

    Texture2D texture2D = ScreenCapture.CaptureScreenshotAsTexture();
    
    • 1

    但只能全屏截图,会截到不需要的UI

    2使用Texture2D读取屏幕像素

            Rect rect = new Rect(0, 0, Screen.width, Screen.height );        
            Texture2D resultTexture = new Texture2D(Screen.width, Screen.height);
            resultTexture.ReadPixels(rect, 0, 0);//将自动读取屏幕上的像素
            resultTexture.Apply();
    
    • 1
    • 2
    • 3
    • 4

    可以自定义读取的矩形区域,Rect的四个参数分别为 起始坐标X,起始坐标Y,从起始坐标向右读多少像素,从起始坐标向上读多少相许 ,例子中的起始坐标(0,0),是屏幕的左下角

    但是这么做的问题在于,依然会截到UI,而且如果是多屏幕,他截取的那个屏幕不一定,根据实验应该是最后激活的屏幕

    3使用相机的RenderTexture
    本方法基于方法2,只不过Textrue2D不再读取屏幕像素,而是读取RenderTexture

            //声明一个临时的渲染纹理,如果不用临时纹理,也可以在Assets里新建一个RenderTexure,拖给相机的targetTexure属性,但是注意有RenderTexture的相机将不会向game窗口输出画面
            RenderTexture renderTexture = new RenderTexture(Screen.width,Screen.height,0);
            uiCamera_2.targetTexture = renderTexture;
            uiCamera_2.Render();//让相机渲染一帧
            //激活渲染纹理,Textrue2D将读取这个纹理的像素,而不读取屏幕,如果不激活,将读取屏幕
            RenderTexture.active = renderTexture; 
    
            Rect rect = new Rect(0, 0, Screen.width, Screen.height );        
            Texture2D resultTexture = new Texture2D(Screen.width, Screen.height);
            resultTexture.ReadPixels(rect, 0, 0);
            resultTexture.Apply();
    
            uiCamera_2.targetTexture = null;//将相机画面重新输出到game窗口
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这么做的好处是,截到的画面只和相机拍到的画面有关,因此可以屏蔽UI,并且不受Display窗口限制


    第三步,文字识别
    研究了一些方法,发现只用unity是不行的(主要是因为识别文字的程序包和Untiy冲突),需要外接c#程序,我使用的是winform程序,通过Unity调用winform程序,通过udp通信完成文字识别

    首先新建一个winform程序
    右键解决方案,打开Nuget包管理
    在这里插入图片描述
    切换到浏览,搜索PaddleOCRSharp,并安装
    在这里插入图片描述
    引入命名空间

    using PaddleOCRSharp;
    
    • 1

    编写代码

     private void GetOCR()
            {
                Bitmap bitmap = new Bitmap("d://ocr.png");
                //启动引擎  引擎启动比较耗时,可以提前启动,待程序完成再释放
                OCRModelConfig config = null;
                OCRParameter oCRParameter = new OCRParameter();
                OCRResult oCRResult = new OCRResult();
                PaddleOCREngine engine = new PaddleOCREngine(config,oCRParameter);
                
                oCRResult = engine.DetectText(bitmap);
    
                Console.WriteLine(oCRResult.Text);
                //释放引擎
                engine.Dispose();
    			//释放图片
                bitmap.Dispose();
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    此处只是识别演示代码,可以实现最基本的识别

    注意:字不可以太大,也不可以太小,会影响结果,不能识别连笔

    工程完整代码
    winform已经放入untiy,Untiy工程可以直接单独运行,无需启动winform
    winform程序用于展示代码

    免积分下载

  • 相关阅读:
    spring cloud 快速上手系列 -> 03-消息队列 Stream -> 033-使用spring cloud bus实现配置中心热刷新-Client
    张驰咨询:家电企业用六西格玛项目减少客户非合理退货案例
    MIT6.s081/6.828 lectrue07:Page faults 以及 Lab5 心得
    CentOS 7 安装详细教程
    POJ3322 Bloxorz I 题解
    Cookie与Session详解
    网络安全-ACL应用
    在 Next.js 中实现用户授权
    (十七)数据结构-图的应用-最小生成树
    CVPR 2022 Paper Reading List
  • 原文地址:https://blog.csdn.net/weixin_44568736/article/details/126786299