OpenCVForUnity之DocumentScannerExample

DocumentScannerExample是OpenCVForUnity库的一个示例场景,用于演示如何使用OpenCVForUnity库实现文档扫描器应用程序。文档扫描器应用程序是一种常见的应用程序,它可以将纸质文档转换为数字格式,并进行后续处理和存储。

在DocumentScannerExample场景中,OpenCVForUnity库的各种功能被用来实现文档扫描器应用程序。其中包括使用相机捕获图像、预处理图像以准备进行文档检测、检测文档边缘、透视变换以纠正图像倾斜和扭曲。

#if !(PLATFORM_LUMIN && !UNITY_EDITOR)

using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using OpenCVForUnity.CoreModule;
using OpenCVForUnity.ImgprocModule;
using OpenCVForUnity.UnityUtils.Helper;
using OpenCVForUnity.UnityUtils;
using System;

namespace OpenCVForUnityExample
{
    /// <summary>
    /// Document Scanner Example
    /// An example of document scanning (like receipts, business cards etc) using the Imgproc class.
    /// 使用Imgproc类扫描文档(如收据、名片等)的示例。
    /// </summary>
    [RequireComponent(typeof(WebCamTextureToMatHelper))]
    public class DocumentScannerExample : MonoBehaviour
    {
        /// <summary>
        /// Determines if debug mode.
        /// 确定是否为调试模式。
        /// </summary>
        public bool isDebugMode = false;

        /// <summary>
        /// The debug mode toggle.
        /// 调试模式切换。
        /// </summary>
        public Toggle isDebugModeToggle;

        Mat yuvMat;//表示一个YUV格式的图像矩阵,原始图像数据将存储在yuvMat中。
        Mat yMat;//表示提取自yuvMat的亮度分量(即Y通道)的图像矩阵,用于文档检测和透视变换等操作。

        Mat displayMat;//表示用于显示的图像矩阵,该矩阵包含了文档检测和透视变换等操作后的结果图像数据。
        Mat inputDisplayAreaMat;//表示输入区域的图像矩阵,用于显示相机捕获的原始图像数据。
        Mat outputDisplayAreaMat;//表示输出区域的图像矩阵,用于显示文档扫描器应用程序的结果图像数据。

        Scalar CONTOUR_COLOR;//表示轮廓的颜色,用于绘制文档轮廓。
        Scalar DEBUG_CONTOUR_COLOR;//表示用于调试轮廓的颜色,用于在调试模式下绘制文档轮廓。
        Scalar DEBUG_CORNER_NUMBER_COLOR;//表示用于调试角点数字的颜色,用于在调试模式下绘制文档角点处的数字。

        /// <summary>
        /// The texture.
        /// 贴图
        /// </summary>
        Texture2D texture;

        /// <summary>
        /// The webcam texture to mat helper.
        /// OpenCVForUnity库中提供的一个帮助类,用于在Unity中将WebCamTexture对象转换为OpenCV中的Mat对象。
        /// </summary>
        WebCamTextureToMatHelper webCamTextureToMatHelper;

        /// <summary>
        /// The FPS monitor.
        /// 帧率监视器
        /// </summary>
        FpsMonitor fpsMonitor;

        // Use this for initialization
        void Start()
        {
            fpsMonitor = GetComponent<FpsMonitor>();

            webCamTextureToMatHelper = gameObject.GetComponent<WebCamTextureToMatHelper>();

#if UNITY_ANDROID && !UNITY_EDITOR
            // Avoids the front camera low light issue that occurs in only some Android devices (e.g. Google Pixel, Pixel2).
            webCamTextureToMatHelper.avoidAndroidFrontCameraLowLightIssue = true;
#endif
            webCamTextureToMatHelper.Initialize();

            isDebugModeToggle.isOn = isDebugMode;
        }

        /// <summary>
        /// Raises the web cam texture to mat helper initialized event.
        /// 将网络摄像头纹理提升到矩阵辅助对象初始化事件。
        /// 该函数是WebCamTextureToMatHelper类中的回调函数,用于在WebCamTextureToMatHelper初始化完成后进行一些处理。
        /// </summary>
        public void OnWebCamTextureToMatHelperInitialized()
        {
            Debug.Log("OnWebCamTextureToMatHelperInitialized");

            // 通过webCamTextureToMatHelper.GetMat()方法获取相机捕获的图像数据
            Mat webCamTextureMat = webCamTextureToMatHelper.GetMat();

            // 根据图像的宽高比例来设置显示Mat矩阵(displayMat)、输入显示区域Mat矩阵(inputDisplayAreaMat)和输出显示区域Mat矩阵(outputDisplayAreaMat)。
            if (webCamTextureMat.width() < webCamTextureMat.height())
            {
                displayMat = new Mat(webCamTextureMat.rows(), webCamTextureMat.cols() * 2, webCamTextureMat.type(), new Scalar(0, 0, 0, 255));
                inputDisplayAreaMat = new Mat(displayMat, new OpenCVForUnity.CoreModule.Rect(0, 0, webCamTextureMat.width(), webCamTextureMat.height()));
                outputDisplayAreaMat = new Mat(displayMat, new OpenCVForUnity.CoreModule.Rect(webCamTextureMat.width(), 0, webCamTextureMat.width(), webCamTextureMat.height()));
            }
            else
            {
                displayMat = new Mat(webCamTextureMat.rows() * 2, webCamTextureMat.cols(), webCamTextureMat.type(), new Scalar(0, 0, 0, 255));
                inputDisplayAreaMat = new Mat(displayMat, new OpenCVForUnity.CoreModule.Rect(0, 0, webCamTextureMat.width(), webCamTextureMat.height()));
                outputDisplayAreaMat = new Mat(displayMat, new OpenCVForUnity.CoreModule.Rect(0, webCamTextureMat.height(), webCamTextureMat.width(), webCamTextureMat.height()));
            }

            // 该Texture2D对象用于在屏幕上显示相机捕获的图像和文档扫描器的结果图像。
            texture = new Texture2D(displayMat.cols(), displayMat.rows(), TextureFormat.RGBA32, false);
            // 通过将Texture2D对象设置为GameObject的主纹理,可以将图像显示在屏幕上。
            gameObject.GetComponent<Renderer>().material.mainTexture = texture;
            // 依据图片比例设置gameObject尺寸
            gameObject.transform.localScale = new Vector3(displayMat.cols(), displayMat.rows(), 1);

            Debug.Log("Screen.width " + Screen.width + " Screen.height " + Screen.height + " Screen.orientation " + Screen.orientation);
            
            //帧率监控器添加元素
            if (fpsMonitor != null)
            {
                fpsMonitor.Add("width", displayMat.width().ToString());
                fpsMonitor.Add("height", displayMat.height().ToString());
                fpsMonitor.Add("orientation", Screen.orientation.ToString());
                fpsMonitor.consoleText = "Please place a document paper (receipt or business card) on a plain background.";
            }

            /// <summary>
            /// 根据屏幕和图像的比例关系来调整相机的正交大小。
            /// </summary> 
            //取displayMat矩阵的宽度和高度,并计算屏幕与图像宽度和高度的比例(widthScale和heightScale)。
            float width = displayMat.width();
            float height = displayMat.height();

            float widthScale = (float)Screen.width / width;
            float heightScale = (float)Screen.height / height;
            // 判断哪个比例更小,根据比例关系来调整相机的正交大小。
            if (widthScale < heightScale)
            {
                // 如果宽度比例更小,则将屏幕高度与屏幕宽度的比例作为参考,根据图像宽度来计算相机正交大小,使得图像的宽度可以完全显示在屏幕上。
                Camera.main.orthographicSize = (width * (float)Screen.height / (float)Screen.width) / 2;
            }
            else
            {
                // 如果高度比例更小,则将屏幕宽度与屏幕高度的比例作为参考,根据图像高度来计算相机正交大小,使得图像的高度可以完全显示在屏幕上。
                Camera.main.orthographicSize = height / 2;
            }

            yuvMat = new Mat();// 用于存储从相机捕获的图像数据。
            yMat = new Mat();// 用于存储yuvMat对象的亮度分量数据。
            CONTOUR_COLOR = new Scalar(255, 0, 0, 255);// 表示在绘制文档轮廓时使用的颜色。
            DEBUG_CONTOUR_COLOR = new Scalar(255, 255, 0, 255);// 在调试模式下绘制文框角点编号时使用的颜色。
            DEBUG_CORNER_NUMBER_COLOR = new Scalar(255, 255, 255, 255);// 在调试模式下绘制文框角点编号时使用的颜色。

            // If the WebCam is front facing, flip the Mat horizontally. Required for successful detection of document.
            // 如果网络摄像头是正面的,请水平翻转矩阵。成功检测文档所必需的。
            if (webCamTextureToMatHelper.IsFrontFacing() && !webCamTextureToMatHelper.flipHorizontal)
            {
                // 前置相机捕获的图像是镜像翻转的,需要进行水平翻转才能正确地显示图像。
                webCamTextureToMatHelper.flipHorizontal = true;
            }
            else if (!webCamTextureToMatHelper.IsFrontFacing() && webCamTextureToMatHelper.flipHorizontal)
            {
                // 后置相机捕获的图像不需要进行水平翻转,否则会导致图像被翻转。
                webCamTextureToMatHelper.flipHorizontal = false;
            }
        }

        /// <summary>
        /// Raises the web cam texture to mat helper disposed event.
        /// 用于在webCamTextureToMatHelper对象被销毁时清理资源。
        /// </summary>
        public void OnWebCamTextureToMatHelperDisposed()
        {
            Debug.Log("OnWebCamTextureToMatHelperDisposed");

            // 检查texture、yuvMat、yMat和displayMat对象是否存在,并释放它们所占用的内存。
            if (texture != null)
            {
                Texture2D.Destroy(texture);
                texture = null;
            }

            if (yuvMat != null)
                yuvMat.Dispose();

            if (yMat != null)
                yMat.Dispose();

            if (displayMat != null)
                displayMat.Dispose();
        }

        /// <summary>
        /// Raises the web cam texture to mat helper error occurred event.
        /// 将网络摄像头纹理提升到矩阵辅助对象发生错误事件。
        /// </summary>
        /// <param name="errorCode">Error code.</param>
        public void OnWebCamTextureToMatHelperErrorOccurred(WebCamTextureToMatHelper.ErrorCode errorCode)
        {
            Debug.Log("OnWebCamTextureToMatHelperErrorOccurred " + errorCode);
        }

        // Update is called once per frame
        void Update()
        {
            if (webCamTextureToMatHelper.IsPlaying() && webCamTextureToMatHelper.DidUpdateThisFrame())// 检查webCamTextureToMatHelper对象是否正在播放并且已经更新了这一帧。
            {
                //获取当前帧的Mat对象
                Mat rgbaMat = webCamTextureToMatHelper.GetMat();

                // change the color space to YUV.
                // 将其颜色空间从RGBA转换为YUV
                Imgproc.cvtColor(rgbaMat, yuvMat, Imgproc.COLOR_RGBA2RGB);
                Imgproc.cvtColor(yuvMat, yuvMat, Imgproc.COLOR_RGB2YUV);
                // grap only the Y component.
                // 提取Y分量
                Core.extractChannel(yuvMat, yMat, 0);

                // 对Y分量进行高斯模糊和边缘检测
                // blur the image to reduce high frequency noises.
                Imgproc.GaussianBlur(yMat, yMat, new Size(3, 3), 0);
                // find edges in the image.
                Imgproc.Canny(yMat, yMat, 50, 200, 3);

                // find contours.
                // 使用findContours()函数找到所有轮廓,并选择面积最大的轮廓。
                List<MatOfPoint> contours = new List<MatOfPoint>();
                Find4PointContours(yMat, contours);

                // pick the contour of the largest area and rearrange the points in a consistent order.
                // 选取最大区域的轮廓,并按一致的顺序重新排列这些点。
                MatOfPoint maxAreaContour = GetMaxAreaContour(contours);
                maxAreaContour = OrderCornerPoints(maxAreaContour);

                // maxAreaContour是一个轮廓对象,size()函数返回轮廓的大小,area()函数返回轮廓的面积。
                // 如果轮廓的面积大于0,则将found设置为true,否则设置为false。因此,这行代码的作用是检查是否找到了一个有效的轮廓。
                bool found = (maxAreaContour.size().area() > 0);
                if (found)
                {
                    // trasform the prospective of original image.
                    // 变换原始图像的视角。
                    // 对输入图像进行透视变换,并将变换后的图像复制到输出图像的中心位置,以便进行显示或进一步处理。
                    using (Mat transformedMat = PerspectiveTransform(rgbaMat, maxAreaContour))// 返回一个经过透视变换后的Mat对象transformedMat。
                    {
                        outputDisplayAreaMat.setTo(new Scalar(0, 0, 0, 255));// 使用setTo函数将输出图像outputDisplayAreaMat的所有像素值设置为黑色(0,0,0,255),即清空输出图像。

                        //检查变换后的图像的大小是否适合放置在输出图像的中心位置。
                        //如果变换后的图像的宽度和高度都小于等于输出图像的宽度和高度,并且变换后的图像总像素数大于等于输出图像总像素数的1/16,则执行下面的代码块。
                        if (transformedMat.width() <= outputDisplayAreaMat.width() && transformedMat.height() <= outputDisplayAreaMat.height()
                            && transformedMat.total() >= outputDisplayAreaMat.total() / 16)
                        {
                            // 计算输出图像和变换后的图像之间的偏移量,以便将变换后的图像居中显示在输出图像中。
                            int x = outputDisplayAreaMat.width() / 2 - transformedMat.width() / 2;
                            int y = outputDisplayAreaMat.height() / 2 - transformedMat.height() / 2;
                            using (Mat dstAreaMat = new Mat(outputDisplayAreaMat, new OpenCVForUnity.CoreModule.Rect(x, y, transformedMat.width(), transformedMat.height())))// 使用一个新的Mat对象dstAreaMat作为输出图像的子区域,并将变换后的图像复制到该子区域中。
                            {
                                transformedMat.copyTo(dstAreaMat);
                            }
                        }
                    }
                }

                // 在调试模式下将处理后的图像显示出来,以便进行调试和验证。
                if (isDebugMode)
                {
                    // draw edge image.绘制边缘图像。
                    Imgproc.cvtColor(yMat, rgbaMat, Imgproc.COLOR_GRAY2RGBA);

                    // draw all found conours.画出所有找到的轮廓。
                    Imgproc.drawContours(rgbaMat, contours, -1, DEBUG_CONTOUR_COLOR, 1);
                }

                // 如果找到了面积最大的轮廓
                if (found)
                {
                    // draw max area contour. 绘制最大面积轮廓。
                    Imgproc.drawContours(rgbaMat, new List<MatOfPoint> { maxAreaContour }, -1, CONTOUR_COLOR, 2);

                    if (isDebugMode)
                    {
                        // draw corner numbers.绘制角编号。
                        // 使用一个for循环遍历最大面积轮廓的所有点,使用Imgproc.putText函数在每个角点处绘制相应的编号。
                        for (int i = 0; i < maxAreaContour.toArray().Length; i++)
                        {
                            var pt = maxAreaContour.get(i, 0);
                            Imgproc.putText(rgbaMat, i.ToString(), new Point(pt[0], pt[1]), Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, DEBUG_CORNER_NUMBER_COLOR, 1, Imgproc.LINE_AA, false);
                        }
                    }
                }

                rgbaMat.copyTo(inputDisplayAreaMat);
               
                /// 使用Utils.matToTexture2D()函数将输出显示区域的Mat对象转换为Texture2D对象,并将其显示在屏幕上。
                Utils.matToTexture2D(displayMat, texture, true, 0, true);
            }
        }

        /// <summary>
        /// 在输入图像中查找四边形轮廓,并将它们存储在一个MatOfPoint类型的向量contours中。
        /// </summary>
        /// <param name="image"></param>输入图像
        /// <param name="contours"></param>输出轮廓
        private void Find4PointContours(Mat image, List<MatOfPoint> contours)
        {
            contours.Clear();//清空contours向量
            List<MatOfPoint> tmp_contours = new List<MatOfPoint>();
            Mat hierarchy = new Mat();
            /// <summary>
            /// Imgproc.findContours函数查找所有轮廓。
            /// 第一个参数是输入图像
            /// 第二个参数是存储所有轮廓的向量
            /// 第三个参数是轮廓的层次结构
            /// 第四个参数是轮廓检索模式,这里使用RETR_EXTERNAL表示只检测最外层轮廓
            /// 第五个参数是轮廓近似方法,这里使用CHAIN_APPROX_SIMPLE表示仅保留轮廓的端点。
            /// </summary>
            Imgproc.findContours(image, tmp_contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

            
            foreach (var cnt in tmp_contours)
            {
                // 对于每个找到的轮廓,使用Imgproc.convexHull函数计算其凸包。
                MatOfInt hull = new MatOfInt();
                Imgproc.convexHull(cnt, hull, false);

                // 轮廓和凸包转换为Point类型的数组,以便进行多边形逼近。
                Point[] cnt_arr = cnt.toArray();
                int[] hull_arr = hull.toArray();
                Point[] pts = new Point[hull_arr.Length];
                for (int i = 0; i < hull_arr.Length; i++)
                {
                    pts[i] = cnt_arr[hull_arr[i]];
                }

                MatOfPoint2f ptsFC2 = new MatOfPoint2f(pts);
                MatOfPoint2f approxFC2 = new MatOfPoint2f();
                MatOfPoint approxSC2 = new MatOfPoint();

                // 使用Imgproc.approxPolyDP函数对凸包进行多边形逼近,并将结果存储在MatOfPoint类型的approxSC2中。
                double arclen = Imgproc.arcLength(ptsFC2, true);
                Imgproc.approxPolyDP(ptsFC2, approxFC2, 0.01 * arclen, true);
                approxFC2.convertTo(approxSC2, CvType.CV_32S);

                // 如果多边形的大小不是4(即不是四边形),则忽略该轮廓。
                // 如果大小为4,则将该轮廓添加到contours向量中。
                if (approxSC2.size().area() != 4)
                    continue;

                contours.Add(approxSC2);
            }
        }

        /// <summary>
        /// 在给定的轮廓列表中找到最大面积的轮廓,并将其返回为MatOfPoint类型。
        /// </summary>
        /// <param name="contours"></param>
        /// <returns></returns>        
        private MatOfPoint GetMaxAreaContour(List<MatOfPoint> contours)
        {
            // 如果传入的轮廓列表为空,则返回一个空的MatOfPoint对象。
            if (contours.Count == 0)
                return new MatOfPoint();

            int index = -1;
            double area = 0;
            // 用一个for循环遍历轮廓列表中的每个轮廓
            for (int i = 0; i < contours.Count; i++)
            {
                // 使用Imgproc.contourArea函数计算每个轮廓的面积。
                double tmp = Imgproc.contourArea(contours[i]);
                //如果计算出来的面积大于之前计算的面积,则更新最大面积和对应的轮廓索引。
                if (area < tmp)
                {
                    area = tmp;
                    index = i;
                }
            }
            // 返回具有最大面积的轮廓,它是一个MatOfPoint类型的对象。如果轮廓列表中有多个具有相同最大面积的轮廓,则只返回第一个。
            return contours[index];
        }

        /// <summary>
        /// 对四个角点进行排序,以便后续的透视变换操作。
        /// </summary>
        /// <param name="corners"></param>角点矩阵
        /// <returns></returns>
        private MatOfPoint OrderCornerPoints(MatOfPoint corners)
        {
            // 代码检查输入的角点矩阵是否为空或大小不足4个,如果是,则直接返回输入的角点矩阵。
            if (corners.size().area() <= 0 || corners.rows() < 4)
                return corners;

            // rearrange the points in the order of upper left, upper right, lower right, lower left.
            // 按左上、右上、右下、左下的顺序重新排列这些点。
            using (Mat x = new Mat(corners.size(), CvType.CV_32SC1))
            using (Mat y = new Mat(corners.size(), CvType.CV_32SC1))
            using (Mat d = new Mat(corners.size(), CvType.CV_32SC1))
            using (Mat dst = new Mat(corners.size(), CvType.CV_32SC2))
            {
                // extractChannel函数从角点矩阵中提取x和y坐标通道
                Core.extractChannel(corners, x, 0);
                Core.extractChannel(corners, y, 1);

                // the sum of the upper left points is the smallest and the sum of the lower right points is the largest.
                // 左上点的和最小,右下点的和最大。
                Core.add(x, y, d);//然后计算每个角点的x坐标和y坐标之和,保存在一个名为d的矩阵中。
                Core.MinMaxLocResult result = Core.minMaxLoc(d);// 从矩阵d中获取最小值和最大值,并将结果存储在名为result的MinMaxLocResult对象中。
                // 将计算出来的左上角点和右下角点的坐标分别存储在dst矩阵的第0行和第2行,以便后续使用。
                dst.put(0, 0, corners.get((int)result.minLoc.y, 0));//左上
                dst.put(2, 0, corners.get((int)result.maxLoc.y, 0));//右下

                // the difference in the upper right point is the smallest, and the difference in the lower left is the largest.
                // 右上角的差异最小,左下角的差异最大。
                Core.subtract(y, x, d);// 计算角点矩阵中每个角点的y坐标减去x坐标得到的差值,并将结果存储在名为d的矩阵中。
                result = Core.minMaxLoc(d);// 从矩阵d中获取最小值和最大值,并将结果存储在名为result的MinMaxLocResult对象中。
                // 将计算出来的右上角点和左下角点的坐标分别存储在dst矩阵的第1行和第3行,以便后续使用。
                dst.put(1, 0, corners.get((int)result.minLoc.y, 0));//右上
                dst.put(3, 0, corners.get((int)result.maxLoc.y, 0));//左下
                // 代码将dst矩阵复制回角点矩阵中,并返回排序后的角点矩阵。
                dst.copyTo(corners);
            }
            return corners;
        }

        /// <summary>
        /// 用于将图像进行透视变换。
        /// </summary>
        /// <param name="image"></param>输入图像
        /// <param name="corners"></param>待变换区域的四个角点
        /// <returns></returns>
        private Mat PerspectiveTransform(Mat image, MatOfPoint corners)
        {
            // 代码检查输入的角点矩阵是否为空或大小不足4个,如果是,则直接返回输入的图像矩阵。
            if (corners.size().area() <= 0 || corners.rows() < 4)
                return image;
            //提取出四个角点
            Point[] pts = corners.toArray();
            Point tl = pts[0];//左上
            Point tr = pts[1];//右上
            Point br = pts[2];//右下
            Point bl = pts[3];//左下
            
            double widthA = Math.Sqrt((br.x - bl.x) * (br.x - bl.x) + (br.y - bl.y) * (br.y - bl.y));//计算底部两个角点的距离
            double widthB = Math.Sqrt((tr.x - tl.x) * (tr.x - tl.x) + (tr.y - tl.y) * (tr.y - tl.y));//计算顶部两个角点的距离
            int maxWidth = Math.Max((int)widthA, (int)widthB);//确定变换后图像的最大宽度
            
            double heightA = Math.Sqrt((tr.x - br.x) * (tr.x - br.x) + (tr.y - br.y) * (tr.y - br.y));//计算右边两个角点的距离
            double heightB = Math.Sqrt((tl.x - bl.x) * (tl.x - bl.x) + (tl.y - bl.y) * (tl.y - bl.y));//计算左边两个角点的距离
            int maxHeight = Math.Max((int)heightA, (int)heightB);确定变换后图像的最大高度
            //宽度和高度至少为1
            maxWidth = (maxWidth < 1) ? 1 : maxWidth;
            maxHeight = (maxHeight < 1) ? 1 : maxHeight;

            // 将角点矩阵的数据类型转换为CV_32FC2(即32位浮点型的二维矩阵),并创建了一个目标矩阵dst,用于存储变换后的四个角点。
            Mat src = new Mat();
            corners.convertTo(src, CvType.CV_32FC2);
            Mat dst = new Mat(4, 1, CvType.CV_32FC2);
            dst.put(0, 0, 0, 0, maxWidth - 1, 0, maxWidth - 1, maxHeight - 1, 0, maxHeight - 1);

            // compute and apply the perspective transformation matrix.
            // 计算并应用透视变换矩阵。
            Mat outputMat = new Mat(maxHeight, maxWidth, image.type(), new Scalar(0, 0, 0, 255));
            Mat perspectiveTransform = Imgproc.getPerspectiveTransform(src, dst);//计算透视变换矩阵perspectiveTransform
            Imgproc.warpPerspective(image, outputMat, perspectiveTransform, new Size(outputMat.cols(), outputMat.rows()));//原图像image进行透视变换,并将变换后的图像存储在Mat类型的outputMat中。

            // return the transformed image.返回变换后的图像
            return outputMat;
        }

        /// <summary>
        /// Raises the destroy event.
        /// </summary>
        void OnDestroy()
        {
            webCamTextureToMatHelper.Dispose();
        }

        /// <summary>
        /// Raises the back button click event.
        /// </summary>
        public void OnBackButtonClick()
        {
            SceneManager.LoadScene("OpenCVForUnityExample");
        }

        /// <summary>
        /// Raises the play button click event.
        /// </summary>
        public void OnPlayButtonClick()
        {
            webCamTextureToMatHelper.Play();
        }

        /// <summary>
        /// Raises the pause button click event.
        /// </summary>
        public void OnPauseButtonClick()
        {
            webCamTextureToMatHelper.Pause();
        }

        /// <summary>
        /// Raises the stop button click event.
        /// </summary>
        public void OnStopButtonClick()
        {
            webCamTextureToMatHelper.Stop();
        }

        /// <summary>
        /// Raises the change camera button click event.
        /// </summary>
        public void OnChangeCameraButtonClick()
        {
            webCamTextureToMatHelper.requestedIsFrontFacing = !webCamTextureToMatHelper.requestedIsFrontFacing;
        }

        /// <summary>
        /// Raises the is debug mode toggle value changed event.
        /// </summary>
        public void OnIsDebugModeToggleValueChanged()
        {
            if (isDebugMode != isDebugModeToggle.isOn)
            {
                isDebugMode = isDebugModeToggle.isOn;
            }
        }
    }
}

#endif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值