Unity使用PP-MattingV2实现人像分割

PP-MattingV2

https://github.com/PaddlePaddle/PaddleSeg/blob/release/2.10/Matting/README_CN.md


主要代码

csharp 复制代码
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using Debug = UnityEngine.Debug;

public class ppmatting : MonoBehaviour
{
    [Header("模型设置")]
    public string modelPath = "ppmattingv2_stdc1_human_512x512.onnx";
    public float confThreshold = 0.65f;

    [Header("输入设置")]
    public Texture2D inputTexture2D;

    [Header("输出设置")]
    public RawImage image;

    private InferenceSession session;
    private int inputWidth = 512;
    private int inputHeight = 512;

    // 输入输出节点名称
    private string inputName = "input";
    private string outputName = "output";

    // 处理结果
    public Texture2D resultTexture;

    void Start()
    {
        InitializeModel();

        // 如果设置了输入纹理,立即处理
        if (inputTexture2D != null)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            image.texture = ProcessInputTexture();
            stopwatch.Stop();
            long lastInferenceTime = stopwatch.ElapsedMilliseconds;
            // 输出耗时信息
            Debug.Log($"推理完成!总耗时: {lastInferenceTime}ms");
        }
    }

    void InitializeModel()
    {
        try
        {
            // 加载模型
            var modelFullPath = System.IO.Path.Combine(Application.streamingAssetsPath, modelPath);
            var ortEnvInstance = OrtEnv.Instance();
            string[] aps = ortEnvInstance.GetAvailableProviders();
            foreach (var ap in aps)
            {
                Debug.Log(ap);
            }
            var options = new SessionOptions();
            //options.AppendExecutionProvider_CPU();
            options.AppendExecutionProvider_CUDA();
            session = new InferenceSession(modelFullPath, options);

            // 获取输入形状
            var inputMeta = session.InputMetadata;
            foreach (var name in inputMeta.Keys)
            {
                inputName = name;
                var shape = inputMeta[name].Dimensions;
                inputHeight = (int)shape[2];
                inputWidth = (int)shape[3];
            }

            Debug.Log($"模型加载成功: {modelPath}, 输入尺寸: {inputWidth}x{inputHeight}");
        }
        catch (Exception e)
        {
            Debug.LogError($"模型加载失败: {e.Message}");
        }
    }

    void Update()
    {

    }

    /// <summary>
    /// 处理输入的Texture2D并返回结果Texture2D
    /// </summary>
    public Texture2D ProcessInputTexture()
    {
        if (inputTexture2D == null)
        {
            Debug.LogWarning("输入纹理为空");
            return null;
        }

        if (session == null)
        {
            Debug.LogError("模型未初始化");
            return null;
        }

        try
        {
            resultTexture = ProcessMatting(inputTexture2D);
            return resultTexture;
        }
        catch (Exception e)
        {
            Debug.LogError($"处理失败: {e.Message}");
            return null;
        }
    }

    /// <summary>
    /// 处理外部传入的Texture2D
    /// </summary>
    public Texture2D ProcessTexture(Texture2D inputTex)
    {
        if (inputTex == null || session == null)
        {
            Debug.LogError("输入纹理为空或模型未初始化");
            return null;
        }

        try
        {
            return ProcessMatting(inputTex);
        }
        catch (Exception e)
        {
            Debug.LogError($"处理失败: {e.Message}");
            return null;
        }
    }

    Texture2D ProcessMatting(Texture2D inputTexture)
    {
        try
        {
            // 预处理
            var inputTensor = Preprocess(inputTexture);

            // 创建输入
            var inputs = new List<NamedOnnxValue>
            {
                NamedOnnxValue.CreateFromTensor(inputName, inputTensor)
            };

            // 推理
            using (var results = session.Run(inputs))
            {
                // 获取输出
                var outputTensor = results.First().AsTensor<float>();

                // 后处理
                return Postprocess(outputTensor, inputTexture.width, inputTexture.height);
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"人像抠图处理失败: {e.Message}");
            return inputTexture;
        }
    }

    Tensor<float> Preprocess(Texture2D texture)
    {
        // 调整尺寸并转换为RGB
        var resizedTexture = ResizeTexture(texture, inputWidth, inputHeight);
        Color32[] pixels = resizedTexture.GetPixels32();

        // 预先分配数据数组
        int totalElements = 1 * 3 * inputHeight * inputWidth;
        float[] dataArray = new float[totalElements];
        var shape = new int[] { 1, 3, inputHeight, inputWidth };

        // 使用明确的数据数组构造函数
        var tensor = new DenseTensor<float>(dataArray, shape, false);

        for (int y = 0; y < inputHeight; y++)
        {
            for (int x = 0; x < inputWidth; x++)
            {
                int index = y * inputWidth + x;
                var pixel = pixels[index];

                // 归一化并设置通道顺序 [R, G, B]
                tensor[0, 0, y, x] = pixel.r / 255.0f; // R
                tensor[0, 1, y, x] = pixel.g / 255.0f; // G
                tensor[0, 2, y, x] = pixel.b / 255.0f; // B
            }
        }

        // 清理临时纹理
        DestroyImmediate(resizedTexture);

        return tensor;
    }

    Texture2D Postprocess(Tensor<float> output, int originalWidth, int originalHeight)
    {
        // 获取alpha通道 [1, 1, H, W] -> [H, W]
        var alphaMap = new float[inputHeight, inputWidth];
        for (int y = 0; y < inputHeight; y++)
        {
            for (int x = 0; x < inputWidth; x++)
            {
                alphaMap[y, x] = output[0, 0, y, x];
            }
        }

        // 调整到原始尺寸
        var resizedAlpha = ResizeAlphaMap(alphaMap, originalWidth, originalHeight);

        // 创建结果纹理(带透明通道的人像)
        var resultTexture = new Texture2D(originalWidth, originalHeight, TextureFormat.RGBA32, false);

        // 如果需要保留原始图像,可以传入原始纹理
        Color[] originalColors = null;
        if (inputTexture2D != null && inputTexture2D.width == originalWidth && inputTexture2D.height == originalHeight)
        {
            originalColors = inputTexture2D.GetPixels();
        }

        for (int y = 0; y < originalHeight; y++)
        {
            for (int x = 0; x < originalWidth; x++)
            {
                float alpha = resizedAlpha[y, x];
                Color pixelColor;

                if (originalColors != null)
                {
                    // 使用原始图像颜色,根据alpha值设置透明度
                    int index = y * originalWidth + x;
                    pixelColor = originalColors[index];
                    pixelColor.a = alpha > confThreshold ? 1.0f : 0.0f;
                }
                else
                {
                    // 简单显示:人像区域白色,背景透明
                    if (alpha > confThreshold)
                    {
                        pixelColor = Color.white; // 人像区域
                    }
                    else
                    {
                        pixelColor = Color.clear; // 背景透明
                    }
                }

                resultTexture.SetPixel(x, y, pixelColor);
            }
        }

        resultTexture.Apply();
        return resultTexture;
    }

    float[,] ResizeAlphaMap(float[,] alphaMap, int newWidth, int newHeight)
    {
        var resized = new float[newHeight, newWidth];
        float scaleX = (float)inputWidth / newWidth;
        float scaleY = (float)inputHeight / newHeight;

        for (int y = 0; y < newHeight; y++)
        {
            for (int x = 0; x < newWidth; x++)
            {
                int srcX = Mathf.Clamp((int)(x * scaleX), 0, inputWidth - 1);
                int srcY = Mathf.Clamp((int)(y * scaleY), 0, inputHeight - 1);
                resized[y, x] = alphaMap[srcY, srcX];
            }
        }

        return resized;
    }

    Texture2D ResizeTexture(Texture2D source, int newWidth, int newHeight)
    {
        var rt = RenderTexture.GetTemporary(newWidth, newHeight);
        Graphics.Blit(source, rt);

        var result = new Texture2D(newWidth, newHeight, TextureFormat.RGBA32, false);
        RenderTexture.active = rt;
        result.ReadPixels(new Rect(0, 0, newWidth, newHeight), 0, 0);
        result.Apply();

        RenderTexture.ReleaseTemporary(rt);
        return result;
    }

    /// <summary>
    /// 设置输入纹理
    /// </summary>
    public void SetInputTexture(Texture2D texture)
    {
        inputTexture2D = texture;
    }

    /// <summary>
    /// 获取处理结果纹理
    /// </summary>
    public Texture2D GetResultTexture()
    {
        return resultTexture;
    }

    void OnDestroy()
    {
        session?.Dispose();

        // 清理结果纹理
        if (resultTexture != null)
        {
            DestroyImmediate(resultTexture);
        }
    }
}

最后是工程地址

https://github.com/xue-fei/pp-matting-unity.git

相关推荐
90后小陈老师32 分钟前
Unity教学 项目2 2D闯关游戏
游戏·unity·游戏引擎
噗噗夹的TA之旅1 小时前
Unity Shader 学习20:URP LitForwardPass PBR 解析
学习·unity·游戏引擎·图形渲染·技术美术
nnsix1 小时前
Unity ReferenceFinder插件 多选资源查找bug解决
unity·游戏引擎·bug
gzroy3 小时前
Unity Shader Graph实现全息瞄准器
unity·游戏引擎
90后小陈老师6 小时前
Unity教学 基础介绍
unity·游戏引擎
90后小陈老师6 小时前
Unity教学 项目3 3D坦克大战
3d·unity·游戏引擎
秦奈8 小时前
Unity复习学习随笔(五):Unity基础
学习·unity·游戏引擎
nnsix9 小时前
Unity ReferenceFinder插件 窗口中选择资源时 同步选择Assets下的资源
java·unity·游戏引擎
麷飞花10 小时前
unity3d scene窗口选中物体, 在 hierarchy高光显示
unity·editor·unity3d·u3d·hierarchy
ۓ明哲ڪ11 小时前
Unity功能——关闭脚本自动编译(Unity2021.3)
unity·游戏引擎