OpenCVForUnity之Comic Filter Example

首先打开场景定位到主脚本:ComicFilterExample,然后定位到主函数:Update,会发现他首先把摄像头mat取出来,再用cvtColor函数将rgba转gray参考:https://blog.csdn.net/keith_bb/article/details/53470170

先转成了灰度图,然后高斯滤波GaussianBlur(处理图像之前一般都要高斯滤波处理一下,对整幅图像进行加权平均的过程)参考:https://blog.csdn.net/vblittleboy/article/details/9187447

然后进行一个简单处理,灰度值小于60的直接变0,60-120之间的采用斜线填充,120以上的变255这样整个图就变成了黑白灰三色。

然后进行边缘检测,再进行颜色反转(输出的边缘为白色,转为黑色),这样最终的效果就出来了!
参考了博客:https://blog.csdn.net/eevee_1/article/details/118632540?ops_request_misc=&request_id=&biz_id=102&utm_term=ComicFilter&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-3-118632540.nonecase&spm=1018.2226.3001.4187
可能是版本变了,所以略有不同。

#if !(PLATFORM_LUMIN && !UNITY_EDITOR)

using OpenCVForUnity.CoreModule;
using OpenCVForUnity.ImgprocModule;
using OpenCVForUnity.UnityUtils;
using OpenCVForUnity.UnityUtils.Helper;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace OpenCVForUnityExample
{
    /// <summary>
    /// Comic Filter Example
    /// An example of image processing (comic filter) using the Imgproc class.
    /// 使用Imgproc类进行图像处理(漫画滤镜)的示例。
    /// Referring to http://dev.classmethod.jp/smartphone/opencv-manga-2/.
    /// </summary>
    [RequireComponent(typeof(WebCamTextureToMatHelper))]
    public class ComicFilterExample : MonoBehaviour
    {

        /// <summary>
        /// The comic filter.
        /// 漫画滤镜。
        /// </summary>
        ComicFilter comicFilter;

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

        /// <summary>
        /// The webcam texture to mat helper.
        /// 相机提额度到矩阵的助手脚本。
        /// </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).
            // 避免仅在某些 Android 设备(例如 Google Pixel、Pixel2)中出现的前置摄像头低光问题。
            webCamTextureToMatHelper.avoidAndroidFrontCameraLowLightIssue = true;
#endif
            webCamTextureToMatHelper.Initialize();
        }

        /// <summary>
        /// Raises the web cam texture to mat helper initialized event.
        /// 将网络摄像头纹理提升到 mat 助手初始化事件。
        /// </summary>
        public void OnWebCamTextureToMatHelperInitialized()
        {
            Debug.Log("OnWebCamTextureToMatHelperInitialized");
            // 从辅助类获取Mat。
            Mat webCamTextureMat = webCamTextureToMatHelper.GetMat();
            // 从Mat创建一个新的Texture2D对象,并将其分配给gameObject的材质的主纹理。
            texture = new Texture2D(webCamTextureMat.cols(), webCamTextureMat.rows(), TextureFormat.RGBA32, false);
            Utils.matToTexture2D(webCamTextureMat, texture);

            gameObject.GetComponent<Renderer>().material.mainTexture = texture;
            // 将gameObject的缩放设置为与Mat的尺寸匹配。
            gameObject.transform.localScale = new Vector3(webCamTextureMat.cols(), webCamTextureMat.rows(), 1);

            Debug.Log("Screen.width " + Screen.width + " Screen.height " + Screen.height + " Screen.orientation " + Screen.orientation);
            // 记录有关屏幕尺寸和方向的信息,并将此信息添加到fpsMonitor对象中(如果存在)。
            if (fpsMonitor != null)
            {
                fpsMonitor.Add("width", webCamTextureMat.width().ToString());
                fpsMonitor.Add("height", webCamTextureMat.height().ToString());
                fpsMonitor.Add("orientation", Screen.orientation.ToString());
            }


            float width = webCamTextureMat.width();
            float height = webCamTextureMat.height();

            float widthScale = (float)Screen.width / width;
            float heightScale = (float)Screen.height / height;
            //根据屏幕尺寸和Mat的尺寸计算相机正交大小的比例因子。
            if (widthScale < heightScale)
            {
                Camera.main.orthographicSize = (width * (float)Screen.height / (float)Screen.width) / 2;
            }
            else
            {
                Camera.main.orthographicSize = height / 2;
            }
            // 用预设的边缘检测算法值创建新的ComicFilter对象。
            int thickness = (Mathf.Max(width, height) <= 640) ? 3 : 5;
            comicFilter = new ComicFilter(60, 120, thickness);
        }

        /// <summary>
        /// Raises the web cam texture to mat helper disposed event.
        /// 将网络摄像头纹理提升到 mat helper 处理事件。用于将网络摄像头纹理转换为Mat的辅助类被销毁时调用。
        /// </summary>
        public void OnWebCamTextureToMatHelperDisposed()
        {
            // 记录日志,表示辅助类已被销毁。
            Debug.Log("OnWebCamTextureToMatHelperDisposed");

            // 销毁ComicFilter对象。
            comicFilter.Dispose();

            // 如果texture对象不为空,则销毁它。
            if (texture != null)
            {
                Texture2D.Destroy(texture);
                texture = null;
            }
        }

        /// <summary>
        /// Raises the web cam texture to mat helper error occurred event.
        /// 将网络摄像头纹理提升到 mat helper 错误发生事件。
        /// </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())//检查是否正在播放网络摄像头纹理,并且是否已经更新了当前帧。
            {
                Mat rgbaMat = webCamTextureToMatHelper.GetMat();// 获取Mat对象,以便进行处理。

                comicFilter.Process(rgbaMat, rgbaMat);//调用ComicFilter对象的Process方法对Mat进行处理。

                //Imgproc.putText(rgbaMat, "W:" + rgbaMat.width() + " H:" + rgbaMat.height() + " SO:" + Screen.orientation, new Point(5, rgbaMat.rows() - 10), Imgproc.FONT_HERSHEY_SIMPLEX, 1.0, new Scalar(255, 255, 255, 255), 2, Imgproc.LINE_AA, false);

                Utils.matToTexture2D(rgbaMat, texture);//将处理后的Mat转换为Texture2D对象。 
            }
        }

        /// <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;
        }
    }
}

#endif
using OpenCVForUnity.CoreModule;
using OpenCVForUnity.ImgprocModule;
using OpenCVForUnity.UtilsModule;
using System;
using UnityEngine;

namespace OpenCVForUnityExample
{

    public class ComicFilter
    {

        /// <summary>
        /// grayMat:用于存储灰度化后的图像的Mat对象。
        /// maskMat:用于存储图像掩膜的Mat对象。
        /// screentoneMat:用于存储图像半色调效果的Mat对象。
        /// grayDstMat:用于存储灰度化后的图像的目标Mat对象。
        /// grayLUT:用于存储灰度化查找表的Mat对象。
        /// contrastAdjustmentsLUT:用于存储对比度调整查找表的Mat对象。
        /// kernel_dilate和kernel_erode:用于存储膨胀和腐蚀过程中的内核的Mat对象。
        /// blurSize:用于存储应用高斯模糊时的内核大小的Size对象。
        /// blackThresh:用于存储黑色阈值的整数值。
        /// drawMainLine:一个布尔值,指示是否绘制主要线条。
        /// useNoiseFilter:一个布尔值,指示是否使用噪声过滤
        /// </summary>
        Mat grayMat;
        Mat maskMat;
        Mat screentoneMat;
        Mat grayDstMat;

        Mat grayLUT;
        Mat contrastAdjustmentsLUT;
        Mat kernel_dilate;
        Mat kernel_erode;
        Size blurSize;
        int blackThresh;
        bool drawMainLine;
        bool useNoiseFilter;


        /// <summary>
        /// ComicFilter.cs文件中的构造函数。构造函数使用传递给它的参数来初始化类的各种成员变量。
        /// </summary>
        /// <param name="blackThresh"></param> 黑色阈值,用于确定图像中哪些像素应该被处理为黑色。默认值为60。
        /// <param name="grayThresh"></param>灰度阈值,用于确定图像中哪些像素应该被处理为灰色。默认值为120。
        /// <param name="thickness"></param>线条粗细,用于确定生成的卡通效果中线条的粗细。默认值为5。
        /// <param name="useNoiseFilter"></param>一个布尔值,指示是否使用噪声过滤。默认值为true。
        public ComicFilter(int blackThresh = 60, int grayThresh = 120, int thickness = 5, bool useNoiseFilter = true)
        {
            // 将传递给它的参数值分配给类的相应成员变量,并使用这些值初始化其它成员变量。
            this.blackThresh = blackThresh;
            this.drawMainLine = (thickness != 0);
            this.useNoiseFilter = useNoiseFilter;

            // 计算灰度查找表grayLUT对象,该查找表将图像中的每个像素值映射到0到255之间的灰度级别。
            // 如果当前元素的值在blackThresh和grayThresh之间,则将LUT中对应像素的值设置为255,否则保持默认值(0)不变。
            // 这个过程的目的是将图像中黑色到灰色之间的像素映射到白色,其他像素映射到黑色。
            grayLUT = new Mat(1, 256, CvType.CV_8UC1);
            byte[] lutArray = new byte[256];
            for (int i = 0; i < lutArray.Length; i++)
            {
                if (blackThresh <= i && i < grayThresh)//i大于黑色阈值但小于灰度阈值
                    lutArray[i] = 255;
            }
            MatUtils.copyToMat(lutArray, grayLUT);

            // 如果线条粗细不为0,则还会计算用于膨胀、腐蚀和高斯模糊的内核,并用它们初始化相应的成员变量。
            if (drawMainLine)
            {
                kernel_dilate = new Mat(thickness, thickness, CvType.CV_8UC1, new Scalar(1));//这个内核用于膨胀操作,可以增加图像中线条的粗细。

                int erode = (thickness >= 5) ? 2 : 1;//根据thickness的值计算腐蚀内核的大小erode。
                kernel_erode = new Mat(erode, erode, CvType.CV_8UC1, new Scalar(1));//这个内核用于腐蚀操作,可以平滑图像中的噪声。

                int blur = (thickness >= 4) ? thickness - 1 : 3;//根据thickness的值计算高斯模糊的内核大小blur。如果thickness大于等于4,则将blur设置为thickness-1,否则将其设置为3。
                blurSize = new Size(blur, blur);//使用blur的值创建一个大小为blur x blur的Size对象blurSize,该对象用于指定高斯模糊的内核大小。
                
                //使用一个名为contrastAdjustmentsLUTArray的byte数组来初始化对比度调整查找表contrastAdjustmentsLUT。
                contrastAdjustmentsLUT = new Mat(1, 256, CvType.CV_8UC1);
                byte[] contrastAdjustmentsLUTArray = new byte[256];
                for (int i = 0; i < contrastAdjustmentsLUTArray.Length; i++)
                {
                    int a = (int)(i * 1.5f);
                    contrastAdjustmentsLUTArray[i] = (a > byte.MaxValue) ? (byte)255 : (byte)a;

                }
                MatUtils.copyToMat(contrastAdjustmentsLUTArray, contrastAdjustmentsLUT);//这个查找表将用于调整卡通图像的对比度。
            }
        }

        /// <summary>
        /// 该方法对源图像(src)进行处理,并将结果保存到目标图像(dst)中。该方法应用各种图像处理技术以创建漫画风格的效果。
        /// </summary>
        /// <param name="src"></param>源图像
        /// <param name="dst"></param>目标图像
        /// <param name="isBGR"></param>布尔标志

        public void Process(Mat src, Mat dst, bool isBGR = false)
        {
            //首先检查源图像和目标图像是否不为空。
            if (src == null)
                throw new ArgumentNullException("src == null");
            if (dst == null)
                throw new ArgumentNullException("dst == null");
            
            // 检查grayMat对象是否为空或其维度是否与源图像匹配。
            // 如果任何条件为真,则该方法会释放grayMat、maskMat、screentoneMat和grayDstMat对象并将它们设置为null。
            if (grayMat != null && (grayMat.width() != src.width() || grayMat.height() != src.height()))
            {
                grayMat.Dispose();
                grayMat = null;
                maskMat.Dispose();
                maskMat = null;
                screentoneMat.Dispose();
                screentoneMat = null;
                grayDstMat.Dispose();
                grayDstMat = null;
            }
            // grayMat、maskMat和grayDstMat对象使用源图像的维度进行实例化。
            // 这段代码使用了null合并运算符(??),它检查grayMat、maskMat和grayDstMat对象是否为null。
            // 如果是null,则使用指定的高度、宽度和类型(CV_8UC1)创建一个新的Mat对象,并将其赋值给grayMat、maskMat和grayDstMat。
            // 如果对象不为null,则不会创建新的Mat对象。这个代码段的作用是确保grayMat、maskMat和grayDstMat对象已经被正确初始化,并且它们的维度与源图像(src)相同。
            grayMat = grayMat ?? new Mat(src.height(), src.width(), CvType.CV_8UC1);
            maskMat = maskMat ?? new Mat(src.height(), src.width(), CvType.CV_8UC1);
            grayDstMat = grayDstMat ?? new Mat(src.height(), src.width(), CvType.CV_8UC1);

            // 如果screentoneMat对象为空,则该方法创建一个带条纹的半色调并将其保存到screentoneMat中。即条纹填充
            if (screentoneMat == null)
            {
                // create a striped screentone.创建一个条纹网点。
                screentoneMat = new Mat(src.height(), src.width(), CvType.CV_8UC1, new Scalar(255));
                for (int i = 0; i < screentoneMat.rows() * 2.5f; i = i + 4)
                {
                    Imgproc.line(screentoneMat, new Point(0, 0 + i), new Point(screentoneMat.cols(), -screentoneMat.cols() + i), new Scalar(0), 1);//绘制填充
                }
            }
            
            /// 检查源图像的类型。
            /// 如果它是灰度图像(CV_8UC1),则将源图像复制到grayMat。
            /// 如果它是彩色图像(CV_8UC3或CV_8UC4),则使用适当的颜色转换函数(COLOR_BGR2GRAY、COLOR_RGB2GRAY、COLOR_BGRA2GRAY或COLOR_RGBA2GRAY)将源图像转换为灰度图像,并将其保存到grayMat。
            if (src.type() == CvType.CV_8UC1)
            {
                src.copyTo(grayMat);
            }
            //使用Imgproc.cvtColor函数进行颜色空间转换后,得到的图像只有一种颜色,即黑白灰阶。
            //因为该函数将源图像从BGR或RGB模式转换为灰度模式,灰度图像只有一个通道,并且每个像素的值仅表示其亮度或灰度,而不包含颜色信息。
            //在灰度图像中,每个像素的颜色值表示它在黑白灰阶中的灰度级别,通常在0到255之间,其中0表示黑色,255表示白色,中间的灰度级别代表着不同程度的灰度。
            //因此,灰度图像可以看作是一种单通道的黑白图像,每个像素的值仅表示其亮度或灰度,而不包含颜色信息。
            else if (src.type() == CvType.CV_8UC3)
            {
                Imgproc.cvtColor(src, grayMat, (isBGR) ? Imgproc.COLOR_BGR2GRAY : Imgproc.COLOR_RGB2GRAY);
            }
            else
            {
                Imgproc.cvtColor(src, grayMat, (isBGR) ? Imgproc.COLOR_BGRA2GRAY : Imgproc.COLOR_RGBA2GRAY);
            }



            // binarize.使用阈值(blackThresh)对灰度图像进行二值化,并将结果保存到grayDstMat中。
            Imgproc.threshold(grayMat, grayDstMat, blackThresh, 255.0, Imgproc.THRESH_BINARY);

            // draw striped screentone.使用查找表(LUT)和maskMat在二值化图像上绘制了一个带条纹的半色调。
            /// <summary>
            /// 灰度查找表(LUT)是一种用于将输入像素值映射到输出像素值的技术。在这里,grayLUT是一个256x1的Mat矩阵,其中每个像素值代表了输入像素值的映射输出值。
            /// </summary>
            Core.LUT(grayMat, grayLUT, maskMat);// 对灰度图像grayMat进行颜色映射操作,将灰度值转换为新的颜色值,保存在maskMat中。黑白之间的部分为1,在screentoneMat.copyTo(grayDstMat, maskMat);会被复制。
            //maskMat = new Mat(src.height(), src.width(), CvType.CV_8UC1, new Scalar(0)); 可以注释掉这行或者取消注释,能看出差异。
            //screentoneMat为得到的填充图像,根据grayLUT求得的maskMat用于指示哪些部分需要填充。
            //screentoneMat为一张全部为填充条纹的Mat,依据maskMat使用copyTo将条纹复制到grayDstMat实现填充效果。
            screentoneMat.copyTo(grayDstMat, maskMat);//使用copyTo函数将screentoneMat复制到grayDstMat中,但只在掩码图像maskMat中对应的像素位置上进行复制。如果掩码图像maskMat中某个像素值为0,则不进行复制,因此输出图像中的颜色数量可能减少。
            //screentoneMat.copyTo(grayDstMat);


            // draw main line.
            if (drawMainLine)
            {
                // 使用LUT函数将grayMat进行对比度调整
                Core.LUT(grayMat, contrastAdjustmentsLUT, maskMat); // = grayMat.convertTo(maskMat, -1, 1.5, 0);

                // 使用模糊滤波和膨胀操作对maskMat进行处理。
                if (useNoiseFilter)
                {
                    Imgproc.blur(maskMat, grayMat, blurSize);// 模糊滤波可以平滑图像并减少噪声。
                    Imgproc.dilate(grayMat, maskMat, kernel_dilate);// 膨胀操作可以将白色区域扩张,使图像中的线条变得更加粗细。
                }
                else
                {
                    Imgproc.dilate(maskMat, grayMat, kernel_dilate);
                }
                // 使用absdiff函数计算grayMat和maskMat之间的差异,并使用threshold函数将结果二值化为二进制掩码。
                Core.absdiff(grayMat, maskMat, grayMat);
                Imgproc.threshold(grayMat, maskMat, 25, 255.0, Imgproc.THRESH_BINARY);
                // 如果启用了噪声过滤,则对掩码进行侵蚀和位反转操作,并使用copyTo函数将掩码应用于grayDstMat。
                if (useNoiseFilter)
                {
                    Imgproc.erode(maskMat, grayMat, kernel_erode);// 使用了OpenCV的erode函数对二值化的掩码图像(maskMat)进行腐蚀操作,以去除噪声并使线条更加细长
                    Core.bitwise_not(grayMat, maskMat);// 使用bitwise_not函数对grayMat进行位反转,将掩码中的白色部分变为黑色,黑色部分变为白色。
                    maskMat.copyTo(grayDstMat, grayMat);// 使用copyTo函数将处理后的掩码(grayMat)应用于grayDstMat中,以过滤掉原始图像中的非线条部分,只保留线条部分。
                }
                // 否则,只需使用bitwise_not函数反转掩码,并将结果应用于grayDstMat。
                else
                {
                    Core.bitwise_not(maskMat, grayMat);
                    grayMat.copyTo(grayDstMat, maskMat);
                }
            }
            
            // 用于将处理后的图像grayDstMat复制到目标图像dst中。这个过程的作用是将处理后的图像应用于目标图像中,以便显示和保存。
            // 它检查目标图像的类型是否为CV_8UC1(灰度图像)。
            // 如果是,则直接将grayDstMat复制到dst中,因为它们的类型相同。
            if (dst.type() == CvType.CV_8UC1)
            {
                grayDstMat.copyTo(dst);
            }
            // 如果目标图像的类型为CV_8UC3(三通道BGR或RGB图像),则使用cvtColor函数将grayDstMat从灰度图像转换为BGR或RGB图像,并将结果保存到dst中。转换时,可以根据isBGR标志变量选择BGR或RGB模式。
            else if (dst.type() == CvType.CV_8UC3)
            {
                Imgproc.cvtColor(grayDstMat, dst, (isBGR) ? Imgproc.COLOR_GRAY2BGR : Imgproc.COLOR_GRAY2RGB);
            }
            // 如果目标图像的类型不是CV_8UC1或CV_8UC3,则使用cvtColor函数将grayDstMat从灰度图像转换为BGRA或RGBA图像,并将结果保存到dst中。
            else
            {
                Imgproc.cvtColor(grayDstMat, dst, (isBGR) ? Imgproc.COLOR_GRAY2BGRA : Imgproc.COLOR_GRAY2RGBA);
            }
        }

        public void Dispose()
        {
            foreach (var mat in new[] { grayMat, maskMat, screentoneMat, grayDstMat, grayLUT, kernel_dilate, kernel_erode, contrastAdjustmentsLUT })
                if (mat != null) mat.Dispose();

            grayDstMat =
            screentoneMat =
            maskMat =
            grayMat =
            grayLUT =
            kernel_dilate =
            kernel_erode =
            contrastAdjustmentsLUT = null;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值