OPenCVForUnity之AlphaBlendingExample

AlphaBlendingExample是OpenCVForUnity库的一个示例程序,用于演示如何在Unity中使用OpenCV库进行图像处理。具体来说,该示例程序演示了如何执行两个图像的alpha混合操作,以创建一个混合图像。

该示例程序的主要功能是定义两个输入图像,并使用OpenCVForUnity库中的函数执行alpha混合操作。该示例程序还提供了一些用户界面元素,例如下拉列表,以允许用户调整混合参数并重新执行alpha混合操作。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

//using Unity.IL2CPP.CompilerServices;

using OpenCVForUnity.CoreModule;
using OpenCVForUnity.ImgprocModule;
using OpenCVForUnity.UnityUtils;
using OpenCVForUnity.UtilsModule;

namespace OpenCVForUnityExample
{
    /// <summary>
    /// Alpha Blending Example
    /// An example of alpha blending in multiple ways.
    /// 以多种方式进行alpha混合的示例。
    /// 
    /// ### How to speed up pixel array access. (optional) ###
    /// 
    /// # IL2CPP Compiler options:
    /// [Il2CppSetOption(Option.ArrayBoundsChecks, false)]
    /// [Il2CppSetOption(Option.NullChecks, false)]
    /// The runtime checks can be enabled or disabled in C# code using the Il2CppSetOptions attribute. To use this attribute, 
    /// find the Il2CppSetOptionsAttribute.cs source file in the IL2CPP directory in the Unity Editor installation on your computer. 
    /// (Data\il2cpp on Windows, Contents/Frameworks/il2cpp on OS X). Copy this source file into the Assets folder in your project.
    /// https://docs.unity3d.com/Manual/IL2CPP-CompilerOptions.html
    /// 
    /// To use these options, need to uncomment the code that enables the feature. 
    /// 
    /// 
    /// # Pointer acccess. (use -unsafe):
    /// (Unity version 2018.1 or later)
    /// Unsafe code may only appear if compiling with /unsafe. Enable "Allow 'unsafe' code" in Player Settings.
    /// 
    /// (older version)
    /// Unsafe code requires the `unsafe' command-line option to be specified.
    /// You need to add a file "smcs.rsp" (or "gmcs.rsp") in your "Assets" directory, which contains the line: -unsafe
    /// https://answers.unity.com/questions/804103/how-to-enable-unsafe-and-use-pointers.html
    /// 
    /// To use this example,  need to add "OPENCV_USE_UNSAFE_CODE" to Scripting Define Symbols in Player Settings.
    /// 
    /// ######
    /// </summary>

    public class AlphaBlendingExample : MonoBehaviour
    {
        //表示了图像大小的枚举
        public enum ImageSize
        {
            Original,
            Large,
            Small
        }

        /// <summary>
        /// The image size.
        /// 图像大小
        /// </summary>
        public ImageSize imageSize = ImageSize.Original;

        /// <summary>
        /// The count dropdown.
        /// 计数下拉列表。
        /// </summary>
        public Dropdown imageSizeDropdown;

        /// <summary>
        /// The count.
        /// 计数
        /// </summary>
        public int count = 100;

        /// <summary>
        /// The image size dropdown.
        /// 图像尺寸下拉菜单。
        /// </summary>
        public Dropdown countDropdown;

        //用于显示各种图像
        public MeshRenderer fgQuad;//显示前景图像;
        public MeshRenderer bgQuad;//显示背景图像;
        public MeshRenderer alphaQuad;//显示 alpha 通道;
        public MeshRenderer dstQuad;//显示混合后的图像。

        Texture2D fgTex;// 存储前景图像的像素数据;
        Texture2D bgTex;// 存储背景图像的像素数据;
        Texture2D alphaTex;// 存储 alpha 通道的像素数据;
        Texture2D dstTex;// 存储混合后的图像像素数据。

        Mat fgMat;//存储前景图像的像素数据;
        Mat bgMat;//存储背景图像的像素数据;
        Mat alphaMat;//存储 alpha 通道的像素数据;
        Mat dstMat;//存储混合后的图像像素数据。

        //这些变量存储的是放大后的图像数据,将原始图像数据放大后,再进行混合处理,可以获得更高质量的混合效果。
        Mat fgMatLarge;
        Mat bgMatLarge;
        Mat alphaMatLarge;
        Mat dstMatLarge;

        //存储的是图像的 ROI(Region of Interest)部分
        //由于图像处理过程比较复杂,因此需要将原始图像分成多个 ROI 进行处理,以提高处理效率和准确性。
        Mat fgMatROI;
        Mat bgMatROI;
        Mat alphaMatROI;
        Mat dstMatROI;

        //这些变量也用于存储图像数据以便于使用 OpenCV 进行图像处理,但与之前提到的变量不同的是,它们都以 _ 开头,这通常表示它们是私有变量,只能在类的内部访问。
        //这些私有变量用于存储图像数据的副本,以便在进行混合处理时保留原始数据。这样做的好处是,在需要重新进行混合处理时,可以避免反复加载和解析图像文件,从而提高处理效率和准确性。
        Mat _fgMat;
        Mat _bgMat;
        Mat _alphaMat;
        Mat _dstMat;

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


        // Use this for initialization
        void Start()
        {
            fpsMonitor = GetComponent<FpsMonitor>();//获取了 FpsMonitor 组件,用于在游戏运行期间监视游戏的帧率。

            imageSizeDropdown.value = (int)imageSize;//图像大小
            countDropdown.value = 2;//多层混合的层数。

            fgTex = Resources.Load("face") as Texture2D;//加载了一个名为 "face" 的资源作为前景纹理
            //建了三个 Texture2D 类型的变量,分别用于存储前景、背景和 alpha 图像的数据。
            bgTex = new Texture2D(fgTex.width, fgTex.height, TextureFormat.RGBA32, false);
            alphaTex = new Texture2D(fgTex.width, fgTex.height, TextureFormat.RGBA32, false);
            dstTex = new Texture2D(fgTex.width, fgTex.height, TextureFormat.RGBA32, false);

            //创建了四个 Mat 类型的变量,用于存储前景、背景、alpha 和混合后图像的数据。
            fgMat = new Mat(fgTex.height, fgTex.width, CvType.CV_8UC3);
            bgMat = new Mat(fgTex.height, fgTex.width, CvType.CV_8UC3);
            alphaMat = new Mat(fgTex.height, fgTex.width, CvType.CV_8UC1);
            dstMat = new Mat(fgTex.height, fgTex.width, CvType.CV_8UC3, new Scalar(0, 0, 0));


            // Generate fgMat.
            // 将前景纹理转换为 Mat 类型的变量 fgMat
            Utils.texture2DToMat(fgTex, fgMat);

            // 使用 Core.flip() 和 Core.bitwise_not() 方法生成了背景图像和 alpha 图像
            // Generate bgMat.
            Core.flip(fgMat, bgMat, 1);//用于水平翻转图像
            Core.bitwise_not(bgMat, bgMat);//用于将图像的像素值取反

            // Generate alphaMat.
            // 生成了 alpha 图像
            for (int r = 0; r < alphaMat.rows(); r++)
            {
                //使用 alphaMat.row(r) 获取该行对应的 Mat 对象,并使用 setTo() 方法将该 Mat 对象中的每一个元素都设置为一个标量值。这个标量值是一个 0-255 的值,表示该像素在混合过程中的权重。
                alphaMat.row(r).setTo(new Scalar(r / (alphaMat.rows() / 256)));
            }
#pragma warning disable 0618//这行代码是 C# 编程语言中的一行编译器指令,用于禁用编译器警告编号为 0618 的警告。
            //该方法将输入的 alphaMat 对象进行极坐标变换,并将结果保存到 alphaMat 对象中。
            //在变换过程中,将矩阵以中心点 (alphaMat.cols() / 2, alphaMat.rows() / 2) 进行旋转,并将旋转后的矩阵的半径大小设置为 alphaMat.rows()。
            /// <summary>
            /// 这行代码是在 OpenCV 中的 Imgproc 类中的一个方法调用,用于将一个 Mat 对象执行极坐标变换。
            /// 具体来说,该方法将输入的 alphaMat 对象进行极坐标变换,并将结果保存到 alphaMat 对象中。
            /// 在变换过程中,将矩阵以中心点 (alphaMat.cols() / 2, alphaMat.rows() / 2) 进行旋转,并将旋转后的矩阵的半径大小设置为 alphaMat.rows()。
            /// 该方法采用了5个参数:
            /// alphaMat:输入的 Mat 对象,表示要进行极坐标变换的矩阵。
            /// alphaMat:输出的 Mat 对象,表示极坐标变换后的矩阵。
            /// new Point(alphaMat.cols() / 2, alphaMat.rows() / 2):旋转中心,表示将矩阵以该点为中心进行旋转。
            /// alphaMat.rows():旋转半径,表示旋转后的矩阵的半径大小。
            /// Imgproc.INTER_CUBIC | Imgproc.WARP_FILL_OUTLIERS | Imgproc.WARP_INVERSE_MAP:插值方法和变换标志,用于指定极坐标变换的插值方法和变换方式。
            /// INTER_CUBIC 表示使用三次样条插值方法进行插值,WARP_FILL_OUTLIERS 表示在变换过程中填充外部的像素值,WARP_INVERSE_MAP 表示使用反向变换进行极坐标变换。
            /// </summary>
            Imgproc.linearPolar(alphaMat, alphaMat, new Point(alphaMat.cols() / 2, alphaMat.rows() / 2), alphaMat.rows(), Imgproc.INTER_CUBIC | Imgproc.WARP_FILL_OUTLIERS | Imgproc.WARP_INVERSE_MAP);
#pragma warning restore 0618


            // Generate large size Mat.
            // 使用了 OpenCV 中的 resize() 方法,用于将输入的 Mat 对象进行放大操作,并将缩放后的结果保存到新的 Mat 对象中。
            // 输入的 Mat 对象作为第一个参数,放大后的 Mat 对象作为第二个参数,new Size() 表示缩放后的大小,2 表示缩放系数,0 表示不使用插值方法。
            fgMatLarge = new Mat();
            bgMatLarge = new Mat();
            alphaMatLarge = new Mat();
            dstMatLarge = new Mat();
            Imgproc.resize(fgMat, fgMatLarge, new Size(), 2, 2, 0);
            Imgproc.resize(bgMat, bgMatLarge, new Size(), 2, 2, 0);
            Imgproc.resize(alphaMat, alphaMatLarge, new Size(), 2, 2, 0);
            Imgproc.resize(dstMat, dstMatLarge, new Size(), 2, 2, 0);

            // Generate small size Mat (ROI).
            // 创建并提取输入的 Mat 对象的子矩阵,从而获取输入 Mat 对象的某个感兴趣区域(ROI)的像素值。
            OpenCVForUnity.CoreModule.Rect rect = new OpenCVForUnity.CoreModule.Rect(127, 127, 256, 256);//创建了一个矩形 rect,表示要提取的感兴趣区域的位置和大小。该矩形的左上角坐标为 (127, 127),宽度和高度均为 256。
            fgMatROI = new Mat(fgMat, rect);
            bgMatROI = new Mat(bgMat, rect);
            alphaMatROI = new Mat(alphaMat, rect);
            dstMatROI = new Mat(dstMat, rect);

            //使用 Utils.matToTexture2D() 方法将 Mat 类型的变量转换为 Texture2D 类型
            Utils.matToTexture2D(fgMat, fgTex, true, 0, true);
            Utils.matToTexture2D(bgMat, bgTex, true, 0, true);
            Utils.matToTexture2D(alphaMat, alphaTex, true, 0, true);
            Utils.matToTexture2D(dstMat, dstTex, true, 0, true);
            //将其绑定到相应的游戏对象上,以便在屏幕上显示。
            fgQuad.GetComponent<Renderer>().material.mainTexture = fgTex;
            bgQuad.GetComponent<Renderer>().material.mainTexture = bgTex;
            alphaQuad.GetComponent<Renderer>().material.mainTexture = alphaTex;
            dstQuad.GetComponent<Renderer>().material.mainTexture = dstTex;
        }

        /// <summary>
        /// 这段代码是一个协程方法 AlphaBlending(),用于进行 alpha 混合操作,并输出混合过程的时间和结果。
        /// </summary>
        /// <param name="action"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        private IEnumerator AlphaBlending(Action action, int count = 100)
        {
            dstMat.setTo(new Scalar(0, 0, 0));//将 dstMat Mat 对象中的所有像素点的值设置为 (0, 0, 
            Utils.matToTexture2D(dstMat, dstTex);//将 dstMat 转换为对应的 Texture2D 对象

            yield return null;//在协程中等待一帧的时间。
                        
            switch (imageSize)//根据 imageSize 的不同取值,选择需要处理的 Mat 对象
            {
                default:
                case ImageSize.Original://处理原图像
                    _fgMat = fgMat;
                    _bgMat = bgMat;
                    _alphaMat = alphaMat;
                    _dstMat = dstMat;
                    break;
                case ImageSize.Large://处理放大图像
                    _fgMat = fgMatLarge;
                    _bgMat = bgMatLarge;
                    _alphaMat = alphaMatLarge;
                    _dstMat = dstMatLarge;
                    break;
                case ImageSize.Small://处理ROI区域
                    _fgMat = fgMatROI;
                    _bgMat = bgMatROI;
                    _alphaMat = alphaMatROI;
                    _dstMat = dstMatROI;
                    break;
            }

            long ms = time(action, count);//记录方法执行的总时间 ms。

            if (imageSize == ImageSize.Large)//如果处理的是大图像,则对图像进行缩放
                Imgproc.resize(dstMatLarge, dstMat, new Size(), 1.0 / 2.0, 1.0 / 2.0, 0);


            Utils.matToTexture2D(dstMat, dstTex);//将 dstMat 转换为对应的 Texture2D 对象,并输出混合过程的时间和结果。

#if UNITY_WSA && ENABLE_DOTNET//只有在 Unity 编辑器的目标平台为 Windows Store Apps (WSA) 且启用了 .NET 编译器时,才会编译指令块内的代码。
            if (fpsMonitor != null)
            {
                fpsMonitor.consoleText = imageSize + " : " + count + " : " + ms + " ms";
            }
            Debug.Log(imageSize + " : " + count + " : " + ms + " ms");
#else
            if (fpsMonitor != null)
            {
                fpsMonitor.consoleText = imageSize + " : " + count + " : " + action.Method.Name + " : " + ms + " ms";
            }
            Debug.Log(imageSize + " : " + count + " : " + action.Method.Name + " : " + ms + " ms");
#endif
        }

        /// <summary>
        /// 定义了一个私有方法 getput(),通过调用 AlphaBlend_getput() 方法实现 alpha 混合操作。
        /// 该方法实现了将 _fgMat 和 _bgMat 分别按照 _alphaMat 中的权重进行混合操作,并将结果存储在 _dstMat 中的过程。
        /// </summary>
        private void getput()
        {
            AlphaBlend_getput(_fgMat, _bgMat, _alphaMat, _dstMat);
        }

        /// <summary>
        /// 这段代码定义了一个私有方法 matOp(),通过调用 AlphaBlend_matOp() 方法实现 alpha 混合操作。
        /// 该方法实现了将 _fgMat 和 _bgMat 分别按照 _alphaMat 中的权重进行混合操作,并将结果存储在 _dstMat 中的过程。
        /// </summary>
        private void matOp()
        {
            AlphaBlend_matOp(_fgMat, _bgMat, _alphaMat, _dstMat);
        }

        /// <summary>
        /// 这段代码定义了一个私有方法 matOp_alpha3c,通过调用  AlphaBlend_matOp_alpha3c() 方法实现 alpha 混合操作。
        /// 该方法实现了将 _fgMat 和 _bgMat 分别按照 _alphaMat 中的权重进行混合操作,并将结果存储在 _dstMat 中的过程。
        /// </summary>
        private void matOp_alpha3c()
        {
            AlphaBlend_matOp_alpha3c(_fgMat, _bgMat, _alphaMat, _dstMat);
        }

        /// <summary>
        /// 这段代码定义了一个私有方法 copyFromMat(),通过调用 AlphaBlend_copyFromMat() 方法实现将 _bgMat 复制到 _dstMat 中的操作。
        /// 该方法实现了将 _bgMat 复制到 _dstMat 中的过程。
        /// </summary>
        private void copyFromMat()
        {
            AlphaBlend_copyFromMat(_fgMat, _bgMat, _alphaMat, _dstMat);
        }

        /// <summary>
        /// 这段代码定义了一个私有方法 marshal(),通过调用 AlphaBlend_Marshal() 方法实现 alpha 混合操作。
        /// 该方法实现了将 _fgMat 和 _bgMat 分别按照 _alphaMat 中的权重进行混合操作,并将结果存储在 _dstMat 中的过程。
        /// </summary>
        private void marshal()
        {
            AlphaBlend_Marshal(_fgMat, _bgMat, _alphaMat, _dstMat);
        }

        /// <summary>
        /// 这段代码定义了一个私有方法 pointerAccess(),通过调用 AlphaBlend_pointerAccess() 方法实现 alpha 混合操作。
        /// 该方法实现了将 _fgMat 和 _bgMat 分别按照 _alphaMat 中的权重进行混合操作,并将结果存储在 _dstMat 中的过程。
        /// </summary>
        private void pointerAccess()
        {
            AlphaBlend_pointerAccess(_fgMat, _bgMat, _alphaMat, _dstMat);
        }

        /// <summary>
        /// 这段代码定义了一个私有方法 time(),该方法用于测量执行某个操作 action count 次所需的时间,并将结果以毫秒为单位返回。
        /// </summary>
        /// <param name="action">要执行的操作</param>
        /// <param name="count">要执行操作的次数</param>
        /// <returns></returns>
        private long time(Action action, int count)
        {
            System.GC.Collect();//测量之前将垃圾收集器清空。

            var tw = new System.Diagnostics.Stopwatch();//创建了一个 System.Diagnostics.Stopwatch 类型的对象 tw,用于测量执行操作所需的时间。
            tw.Start();//开始计时
            for (int i = 0; i < count; i++)//通过一个 for 循环执行 action 操作 count 次
                action();
            tw.Stop();//调用 tw.Stop() 方法停止计时

            System.GC.Collect();//调用 System.GC.Collect() 方法,以便在测量之后将垃圾收集器清空。

            return tw.ElapsedMilliseconds;//返回 tw.ElapsedMilliseconds 属性值,该值表示执行操作所需的总时间(以毫秒为单位)。
        }

        // mat.get() mat.put()
        //        [Il2CppSetOption(Option.ArrayBoundsChecks, false)]
        //        [Il2CppSetOption(Option.NullChecks, false)]
        /// <summary>
        /// 现了一个 alpha 混合操作,将前景图像 fg 和背景图像 bg 按照 alpha 通道中的权重进行混合,结果保存在目标图像 dst 中。
        /// </summary>
        /// <param name="fg">前景图像</param>
        /// <param name="bg">背景图像</param>
        /// <param name="alpha">混合权重的alpha矩阵 </param>
        /// <param name="dst">目标图像</param>
        private void AlphaBlend_getput(Mat fg, Mat bg, Mat alpha, Mat dst)
        {
            //声明了三个字节数组 fg_byte、bg_byte 和 alpha_byte,分别用于存储前景图像、背景图像和 alpha 通道的像素值。
            //通过调用 get() 方法,将 fg、bg 和 alpha 三个图像的像素值存储到相应的字节数组中。
            byte[] fg_byte = new byte[fg.total() * fg.channels()];
            fg.get(0, 0, fg_byte);
            byte[] bg_byte = new byte[bg.total() * bg.channels()];
            bg.get(0, 0, bg_byte);
            byte[] alpha_byte = new byte[alpha.total() * alpha.channels()];
            alpha.get(0, 0, alpha_byte);


            int pixel_i = 0;//包含的分量总数
            int channels = (int)bg.channels();//通道数
            int total = (int)bg.total();//总像素数

            ///这段代码实现了 alpha 混合操作,将前景图像 fg 和背景图像 bg 按照 alpha 通道中的权重进行混合,结果保存在目标图像 dst 中。
            ///在 for 循环中,对于每个像素,根据 alpha 值的大小,分别进行不同的操作。
            for (int i = 0; i < total; i++)
            {
                if (alpha_byte[i] == 0)//当 alpha 值为 0 时,表示前景图像中的像素应该完全被忽略,因此代码中不进行任何操作。
                {
                }
                else if (alpha_byte[i] == 255)//当 alpha 值为 255 时,表示前景图像中的像素完全覆盖背景图像中的像素,因此代码将前景图像中的像素值直接赋值给背景图像中的像素值。
                {
                    bg_byte[pixel_i] = fg_byte[pixel_i];
                    bg_byte[pixel_i + 1] = fg_byte[pixel_i + 1];
                    bg_byte[pixel_i + 2] = fg_byte[pixel_i + 2];
                }
                else//当 alpha 值在 0 和 255 之间时,需要对前景图像和背景图像中的像素进行加权混合。然后将 result 转换为字节类型,并将其赋值给背景图像的相应像素位置
                {
                    bg_byte[pixel_i] = (byte)((fg_byte[pixel_i] * alpha_byte[i] + bg_byte[pixel_i] * (255 - alpha_byte[i])) >> 8);
                    bg_byte[pixel_i + 1] = (byte)((fg_byte[pixel_i + 1] * alpha_byte[i] + bg_byte[pixel_i + 1] * (255 - alpha_byte[i])) >> 8);
                    bg_byte[pixel_i + 2] = (byte)((fg_byte[pixel_i + 2] * alpha_byte[i] + bg_byte[pixel_i + 2] * (255 - alpha_byte[i])) >> 8);
                }
                pixel_i += channels;//通过 pixel_i += channels 将 pixel_i 更新为下一个像素的位置,以便在字节数组 bg_byte 中正确地访问下一个像素的像素值。
            }

            dst.put(0, 0, bg_byte);//将混合写入目标图像 dst 的左上角位置 (0, 0) 处。
        }

        // Mat operation
        /// <summary>
        /// 实现了图像的 alpha 混合操作,将前景图像 fg 和背景图像 bg 按照给定的 alpha 通道进行混合,得到混合后的图像 dst。
        /// </summary>
        /// <param name="fg">前景图像</param>
        /// <param name="bg">背景图像</param>
        /// <param name="alpha"></param>
        /// <param name="dst">混合后的图像</param>
        private void AlphaBlend_matOp(Mat fg, Mat bg, Mat alpha, Mat dst)
        {
            List<Mat> channels = new List<Mat>();

            using (Mat _bg = new Mat())
            using (Mat inv_alpha = new Mat(alpha.width(), alpha.height(), alpha.type()))
            {
                Core.bitwise_not(alpha, inv_alpha);//对 alpha 通道进行求反操作,得到 inv_alpha,即255-每个分量

                Core.split(bg, channels);//将背景图像 bg 拆分成三个通道(BGR 通道)
                ///将 inv_alpha 乘以每个通道,得到 inv_alpha_b, inv_alpha_g, inv_alpha_r
                Core.multiply(inv_alpha, channels[0], channels[0], 1.0 / 255);
                Core.multiply(inv_alpha, channels[1], channels[1], 1.0 / 255);
                Core.multiply(inv_alpha, channels[2], channels[2], 1.0 / 255);
                Core.merge(channels, _bg);//将三个通道合并成一个新的图像 _bg

                using (Mat _fg = new Mat())
                {
                    Core.split(fg, channels);//将前景图像 fg 同样拆分成三个通道
                    Core.multiply(alpha, channels[0], channels[0], 1.0 / 255);
                    Core.multiply(alpha, channels[1], channels[1], 1.0 / 255);
                    Core.multiply(alpha, channels[2], channels[2], 1.0 / 255);//将 alpha 通道乘以每个通道
                    Core.merge(channels, _fg);//将三个通道合并成一个新的图像 _fg

                    Core.add(_fg, _bg, dst);//将 _fg 和 _bg 进行加法操作,得到混合后的图像 dst。
                }
            }
        }

        // Mat operation (3channel alpha)
        /// <summary>
        /// 实现了两张图像的 alpha 混合操作,并将结果存储在 dst 中。
        /// </summary>
        /// <param name="fg">前景图像</param>
        /// <param name="bg">背景图像</param>
        /// <param name="alpha">alpha 通道图像</param>
        /// <param name="dst">混合结果图像 dst</param>
        private void AlphaBlend_matOp_alpha3c(Mat fg, Mat bg, Mat alpha, Mat dst)
        {
            using (Mat inv_alpha = new Mat(alpha.width(), alpha.height(), alpha.type()))//创建一个与 alpha 大小和类型相同的矩阵 inv_alpha
            using (Mat alpha3c = new Mat())//创建一个与 alpha 大小和类型相同的三通道矩阵 alpha3c
            using (Mat inv_alpha3c = new Mat())//创建一个与 alpha3c 大小和类型相同的反码矩阵 inv_alpha3c
            {
                List<Mat> channels = new List<Mat>();
                channels.Add(alpha);
                channels.Add(alpha);
                channels.Add(alpha);
                Core.merge(channels, alpha3c);//将 alpha 复制到三个通道中,得到一个三通道的 alpha 通道图像。

                Core.bitwise_not(alpha, inv_alpha);//对 alpha 进行按位取反操作,得到其反码,将结果存储在 inv_alpha 中。

                channels.Clear();
                channels.Add(inv_alpha);
                channels.Add(inv_alpha);
                channels.Add(inv_alpha);
                Core.merge(channels, inv_alpha3c);//将 inv_alpha 复制到三个通道中,得到一个三通道的反码图像。

                using (Mat _bg = new Mat())
                using (Mat _fg = new Mat())
                {
                    Core.multiply(inv_alpha3c, bg, _bg, 1.0 / 255);//将背景图像与反码矩阵 inv_alpha3c 进行逐元素乘法操作,得到一个与 bg 大小和类型相同的矩阵 _bg
                    Core.multiply(alpha3c, fg, _fg, 1.0 / 255);//将前景图像与 alpha 通道矩阵 alpha3c 进行逐元素乘法操作,得到一个与 fg 大小和类型相同的矩阵 _fg
                    Core.add(_fg, _bg, dst);//将 _fg 和 _bg 进行逐元素相加操作,得到混合结果图像 dst。
                }
            }
        }

        // MatUtils.copyFromMat
        //        [Il2CppSetOption(Option.ArrayBoundsChecks, false)]
        //        [Il2CppSetOption(Option.NullChecks, false)]
        /// <summary>
        /// 实现了两张图像的 alpha 混合操作,并将结果存储在 dst 中。
        /// </summary>
        /// <param name="fg">前景图像</param>
        /// <param name="bg">背景图像 </param>
        /// <param name="alpha">alpha 通道图像</param>
        /// <param name="dst">混合结果图像 dst</param>
        private void AlphaBlend_copyFromMat(Mat fg, Mat bg, Mat alpha, Mat dst)
        {
            byte[] fg_byte = new byte[fg.total() * fg.channels()];//创建一个大小为前景图像 fg 总像素数乘以通道数的字节数组 fg_byte
            MatUtils.copyFromMat<byte>(fg, fg_byte);//将 fg 的像素值复制到 fg_byte数组中
            byte[] bg_byte = new byte[bg.total() * bg.channels()];//创建一个大小为背景图像 bg 总像素数乘以通道数的字节数组 bg_byte
            MatUtils.copyFromMat<byte>(bg, bg_byte);//将 bg 的像素值复制到 bg_byte数组中
            byte[] alpha_byte = new byte[alpha.total() * alpha.channels()];//创建一个大小为 alpha 通道图像 alpha 总像素数乘以通道数的字节数组 alpha_byte
            MatUtils.copyFromMat<byte>(alpha, alpha_byte);//将 alpha 的像素值复制到alpha_byte数组中。

            int pixel_i = 0;//包含的分量总数
            int channels = (int)bg.channels();//通道数
            int total = (int)bg.total();//总像素数
            

            for (int i = 0; i < total; i++)//遍历 bg_byte 数组中的每个
            {
                if (alpha_byte[i] == 0)//如果 alpha 通道值为 0,则不进行混合操作。
                {
                }
                else if (alpha_byte[i] == 255)//如果 alpha 通道值为 255,则将前景图像的像素值直接赋值给背景图像的对应位置。
                {
                    bg_byte[pixel_i] = fg_byte[pixel_i];
                    bg_byte[pixel_i + 1] = fg_byte[pixel_i + 1];
                    bg_byte[pixel_i + 2] = fg_byte[pixel_i + 2];
                }
                else//如果 alpha 通道值在 0 和 255 之间,则根据 alpha 通道值对前景图像和背景图像的像素值进行混合操作,并将混合结果赋值给背景图像的对应位置。
                {
                    bg_byte[pixel_i] = (byte)((fg_byte[pixel_i] * alpha_byte[i] + bg_byte[pixel_i] * (255 - alpha_byte[i])) >> 8);
                    bg_byte[pixel_i + 1] = (byte)((fg_byte[pixel_i + 1] * alpha_byte[i] + bg_byte[pixel_i + 1] * (255 - alpha_byte[i])) >> 8);
                    bg_byte[pixel_i + 2] = (byte)((fg_byte[pixel_i + 2] * alpha_byte[i] + bg_byte[pixel_i + 2] * (255 - alpha_byte[i])) >> 8);
                }
                pixel_i += channels;
            }

            MatUtils.copyToMat(bg_byte, dst);//将混合后的背景图像 bg_byte 复制到 dst 中。
        }

        // Marshal
        //        [Il2CppSetOption(Option.ArrayBoundsChecks, false)]
        //        [Il2CppSetOption(Option.NullChecks, false)]
        /// <summary>
        /// 用于在前景图像(fg)、背景图像(bg)和alpha通道(alpha)之间执行alpha混合操作。结果图像存储在dst中。
        /// </summary>
        /// <param name="fg">前景图像</param>
        /// <param name="bg">背景图像</param>
        /// <param name="alpha">alpha通道</param>
        /// <param name="dst">混合结果图像</param>
        private void AlphaBlend_Marshal(Mat fg, Mat bg, Mat alpha, Mat dst)
        {
            //为所有参数矩阵创建了一个字节数组和IntPtr
            byte[] fg_byte = new byte[fg.total() * fg.channels()];
            IntPtr fg_ptr = new IntPtr(fg.dataAddr());
            byte[] bg_byte = new byte[bg.total() * bg.channels()];
            IntPtr bg_ptr = new IntPtr(bg.dataAddr());
            byte[] alpha_byte = new byte[alpha.total() * alpha.channels()];
            IntPtr alpha_ptr = new IntPtr(alpha.dataAddr());
            byte[] dst_byte = new byte[dst.total() * dst.channels()];
            IntPtr dst_ptr = new IntPtr(dst.dataAddr());

            if (fg.isContinuous())//判断矩阵fg是否存储在连续的内存中。当矩阵数据在内存中是按行连续存储时,称为连续存储;否则称为非连续存储。
            {
                //使用Marshal.Copy方法将数据复制到字节数组中。
                Marshal.Copy(fg_ptr, fg_byte, 0, fg_byte.Length);
                Marshal.Copy(bg_ptr, bg_byte, 0, bg_byte.Length);
                Marshal.Copy(alpha_ptr, alpha_byte, 0, alpha_byte.Length);
                Marshal.Copy(dst_ptr, dst_byte, 0, dst_byte.Length);

            }
            else
            {
                //定位背景图像(bg)的感兴趣区域(ROI)并获取其偏移量(ofs)和整个图像的尺寸(wholeSize)
                Size wholeSize = new Size();
                Point ofs = new Point();
                bg.locateROI(wholeSize, ofs);

                //跨度指的是矩阵中相邻两行(或列)之间的字节数。
                long stride = (long)wholeSize.width * bg.elemSize();//计算背景图像(bg)的跨度(stride和alpha_stride),其值等于整个图像宽度(整个图像的宽度为wholeSize.width)乘以每个元素的大小(每个元素的大小为bg.elemSize())
                int w = bg.cols() * bg.channels();//计算背景图像(bg)的宽度(w)。等于背景图像矩阵每行的像素数(bg.cols())乘以每个像素的通道数(bg.channels())
                int h = bg.rows();//计算背景图像(bg)的和高度(h)。等于背景图像矩阵的行数(bg.rows())。
                long alpha_stride = (long)wholeSize.width * alpha.channels();//计算alpha通道矩阵的跨度(stride和alpha_stride)
                int alpha_w = alpha.cols() * alpha.channels();计算alpha通道矩阵的宽度(alpha_w)。
                for (int y = 0; y < h; y++)
                {
                    //使用Marshal.Copy函数从fg、bg和alpha通道矩阵、dst中分别拷贝一行数据到对应的数组中(fg_byte、bg_byte和alpha_byte、dst_byt)
                    Marshal.Copy(fg_ptr, fg_byte, y * w, w);
                    Marshal.Copy(bg_ptr, bg_byte, y * w, w);
                    Marshal.Copy(alpha_ptr, alpha_byte, y * alpha_w, alpha_w);
                    Marshal.Copy(dst_ptr, dst_byte, y * w, w);
                    
                    //根据stride和alpha_stride的值,更新fg_ptr、bg_ptr、alpha_ptr和dst_ptr指针,使其指向下一行数据的起始位置
                    fg_ptr = new IntPtr(fg_ptr.ToInt64() + stride);
                    bg_ptr = new IntPtr(bg_ptr.ToInt64() + stride);
                    alpha_ptr = new IntPtr(alpha_ptr.ToInt64() + alpha_stride);
                    dst_ptr = new IntPtr(dst_ptr.ToInt64() + stride);
                }
            }


            int pixel_i = 0;//包含的分量总数
            int channels = (int)bg.channels();//通道数
            int total = (int)bg.total();//总像素数

            for (int i = 0; i < total; i++)
            {
                if (alpha_byte[i] == 0)//如果 alpha 通道值为 0,则不进行混合操作。
                {
                }
                else if (alpha_byte[i] == 255)//如果 alpha 通道值为 255,则将前景图像的像素值直接赋值给背景图像的对应位置。
                {
                    bg_byte[pixel_i] = fg_byte[pixel_i];
                    bg_byte[pixel_i + 1] = fg_byte[pixel_i + 1];
                    bg_byte[pixel_i + 2] = fg_byte[pixel_i + 2];
                }
                else//如果 alpha 通道值在 0 和 255 之间,则根据 alpha 通道值对前景图像和背景图像的像素值进行混合操作,并将混合结果赋值给背景图像的对应位置。
                {
                    bg_byte[pixel_i] = (byte)((fg_byte[pixel_i] * alpha_byte[i] + bg_byte[pixel_i] * (255 - alpha_byte[i])) >> 8);
                    bg_byte[pixel_i + 1] = (byte)((fg_byte[pixel_i + 1] * alpha_byte[i] + bg_byte[pixel_i + 1] * (255 - alpha_byte[i])) >> 8);
                    bg_byte[pixel_i + 2] = (byte)((fg_byte[pixel_i + 2] * alpha_byte[i] + bg_byte[pixel_i + 2] * (255 - alpha_byte[i])) >> 8);
                }
                pixel_i += channels;
            }


            if (fg.isContinuous())//判断矩阵fg是否存储在连续的内存中。
            {
                Marshal.Copy(bg_byte, 0, dst_ptr, bg_byte.Length);//将最终的混合图像数据从字节数组(bg_byte)拷贝到目标图像的内存块(dst_ptr)中。
            }
            else
            {
                dst_ptr = new IntPtr(dst.dataAddr());//通过dst.dataAddr()获取目标图像的内存块指针

                Size wholeSize = new Size();
                Point ofs = new Point();
                bg.locateROI(wholeSize, ofs);//通过bg.locateROI()获取源图像(bg)的ROI(感兴趣区域)大小和偏移量

                long stride = (long)wholeSize.width * bg.elemSize();//计算出背景图像的stride(即行跨度,每行像素数据占用的字节数)。
                int w = bg.cols() * bg.channels();//计算出背景图像宽度。
                int h = bg.rows();//计算出背景图像的的高度。
                for (int y = 0; y < h; y++)//将源图像中的每一行像素数据拷贝到目标图像中
                {
                    Marshal.Copy(bg_byte, y * w, dst_ptr, w);

                    dst_ptr = new IntPtr(dst_ptr.ToInt64() + stride);
                }
            }
        }

        // pointer access
        //        [Il2CppSetOption(Option.ArrayBoundsChecks, false)]
        //        [Il2CppSetOption(Option.NullChecks, false)]
        /// <summary>
        /// 通过指针访问来对两个输入图像fg和bg进行alpha混合,使用alpha通道图alpha,并将结果存储在dst中。这种混合操作是在C#中使用不安全技术直接操作内存地址的一种方法。
        /// </summary>
        /// <param name="fg"></param>
        /// <param name="bg"></param>
        /// <param name="alpha"></param>
        /// <param name="dst"></param>
        private void AlphaBlend_pointerAccess(Mat fg, Mat bg, Mat alpha, Mat dst)
        {

#if OPENCV_USE_UNSAFE_CODE

            IntPtr fg_ptr = new IntPtr(fg.dataAddr());
            IntPtr bg_ptr = new IntPtr(bg.dataAddr());
            IntPtr alpha_ptr = new IntPtr(alpha.dataAddr());
            IntPtr dst_ptr = new IntPtr(dst.dataAddr());

            if (fg.isContinuous())//如果输入图像fg在内存中是连续的,则该方法在一个循环中对整个图像执行alpha混合。
            {
                int total = (int)bg.total();

                unsafe
                {
                    byte* fg_p = (byte*)fg_ptr;
                    byte* bg_p = (byte*)bg_ptr;
                    byte* alpha_p = (byte*)alpha_ptr;
                    byte* dst_p = (byte*)dst_ptr;

                    //循环遍历图像中的每个像素,并使用alpha通道图alpha更新输出图像dst中对应的像素。
                    //使用指向图像数据的指针访问像素值,并使用alpha通道图alpha作为权重对前景和背景像素值进行加权和混合操作。
                    for (int i = 0; i < total; i++)
                    {
                        *dst_p = (byte)(((*fg_p) * (*alpha_p) + (*bg_p) * (255 - *alpha_p)) >> 8);
                        fg_p++; bg_p++; dst_p++;
                        *dst_p = (byte)(((*fg_p) * (*alpha_p) + (*bg_p) * (255 - *alpha_p)) >> 8);
                        fg_p++; bg_p++; dst_p++;
                        *dst_p = (byte)(((*fg_p) * (*alpha_p) + (*bg_p) * (255 - *alpha_p)) >> 8);
                        fg_p++; bg_p++; dst_p++;

                        alpha_p++;
                    }
                }
            }
            else//如果输入图像fg在内存中不是连续的,则该方法分别对图像的每一行执行alpha混合。
            {
                Size wholeSize = new Size();
                Point ofs = new Point();
                bg.locateROI(wholeSize, ofs);//首先确定输入图像bg的大小和偏移量

                long stride = (long)wholeSize.width * bg.channels();//使用这些信息计算每一行的跨度。
                int w = bg.cols() * bg.channels();//使用这些信息计算每一行的宽度。
                int h = bg.rows();
                long alpha_stride = (long)wholeSize.width * alpha.channels();
                int alpha_w = alpha.cols();

                unsafe
                {
                    byte* fg_p = (byte*)fg_ptr;
                    byte* bg_p = (byte*)bg_ptr;
                    byte* alpha_p = (byte*)alpha_ptr;
                    byte* dst_p = (byte*)dst_ptr;

                    for (int y = 0; y < h; y++)//遍历图像的每一行。使用alpha通道图alpha更新输出图像dst中对应的像素,类似于前一个情况。
                    {
                        for (int x = 0; x < alpha_w; x++)
                        {
                            *dst_p = (byte)(((*fg_p) * (*alpha_p) + (*bg_p) * (255 - *alpha_p)) >> 8);
                            fg_p++; bg_p++; dst_p++;
                            *dst_p = (byte)(((*fg_p) * (*alpha_p) + (*bg_p) * (255 - *alpha_p)) >> 8);
                            fg_p++; bg_p++; dst_p++;
                            *dst_p = (byte)(((*fg_p) * (*alpha_p) + (*bg_p) * (255 - *alpha_p)) >> 8);
                            fg_p++; bg_p++; dst_p++;

                            alpha_p++;
                        }

                        fg_p += stride - w;
                        bg_p += stride - w;
                        alpha_p += alpha_stride - alpha_w;
                        dst_p += stride - w;
                    }
                }
            }

#endif

        }


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

        /// <summary>
        /// Raises the image size dropdown value changed event.
        /// </summary>
        public void OnImageSizeDropdownValueChanged(int result)
        {
            if ((int)imageSize != result)
            {
                imageSize = (ImageSize)result;

            }
        }

        /// <summary>
        /// Raises the count dropdown value changed event.
        /// </summary>
        public void OnCountDropdownValueChanged(int result)//切换count即执行次数
        {
            switch (result)
            {
                default:
                case 0:
                    count = 1;
                    break;
                case 1:
                    count = 10;
                    break;
                case 2:
                    count = 100;
                    break;
            }
        }

        /// <summary>
        /// Raises the getput button click event.
        /// </summary>
        public void OnGetPutButtonClick()
        {
            StartCoroutine(AlphaBlending(getput, count));//启动了名为AlphaBlending的协程,采用AlphaBlend_getput执行混合count次。
        }

        /// <summary>
        /// Raises the MatOp button click event.
        /// </summary>
        public void OnMatOpButtonClick()
        {
            StartCoroutine(AlphaBlending(matOp, count));//启动了名为AlphaBlending的协程,采用AlphaBlend_matOp执行混合count次。
        }

        /// <summary>
        /// Raises the MatOpAlpha3c button click event.
        /// </summary>
        public void OnMatOpAlpha3cButtonClick()
        {
            StartCoroutine(AlphaBlending(matOp_alpha3c, count));//启动了名为AlphaBlending的协程,采用AlphaBlend_matOp_alpha3c执行混合count次。
        }

        /// <summary>
        /// Raises the copyFromMat button click event.
        /// </summary>
        public void OnCopyFromMatButtonClick()
        {
            StartCoroutine(AlphaBlending(copyFromMat, count));//启动了名为AlphaBlending的协程,采用AlphaBlend_copyFromMat执行混合count次。
        }

        /// <summary>
        /// Raises the Marshal button click event.
        /// </summary>
        public void OnMarshalButtonClick()
        {
            StartCoroutine(AlphaBlending(marshal, count));//启动了名为AlphaBlending的协程,采用AlphaBlend_pointerAccess执行混合count次。
        }

        /// <summary>
        /// Raises the pointer access button click event.
        /// </summary>
        public void OnPointerAccessButtonClick()
        {
#if OPENCV_USE_UNSAFE_CODE
            StartCoroutine(AlphaBlending(pointerAccess, count));//启动了名为AlphaBlending的协程,采用AlphaBlend_matOp_alpha3c执行混合count次。
#else
            Debug.LogWarning("To use this example,  need to add \"OPENCV_USE_UNSAFE_CODE\" to Scripting Define Symbols in Player Settings. In addition, unsafe code requires the `unsafe' command-line option to be specified.");
#endif
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值