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