c#实现SharedMatting抠图算法

内容简介

       将Alpha Matting抠图算法由c++ 版本移植至c#环境。 主要采用OpenCV的C#版本Emgu取代c++支撑的OpenCV。

 

参考资料

http://www.inf.ufrgs.br/~eslgastal/SharedMatting/

这个网页是算法的论文介绍页,可以从该网页下载到论文原文以及测试用图以及linux下的Demo。

https://github.com/np-csu/AlphaMatting
我从该网页下载了Alpha Matting算法的c++源码。

https://www.cnblogs.com/Imageshop/p/3550185.html
这是我在查询Alpha Matting算法资料时看见的比较友善的算法介绍。作者优化了C++版的算法。

 

我的实现效果

分享图片

分享图片

?

分享图片

?

分享图片

?

分享图片

?

分享图片

?

 

实验环境

系统: Windows 8.1 专业版

工具:Visual Studio 2017

Emgu:emgucv-windesktop 3.2.0.2682

C#测试工程: WPF工程项目

 

避免采坑

  1. 最好不要用最新版Vs2019

  我刚开始采用VS2019,安装OpenCv后测试运行C++,各种不通。后续发现vs2019新建的项目自动配置为VC16 环境。 而下载的OpenCv明确指定了需要VC14或VC15. 如我下载的OpenCV: opencv-3.4.5-vc14_vc15.exe. 从名称即可看出。废了我许多不必要的尝试时间。

         2、版本差异

  刚开始下载OpenCV 最新的4.1.1 版本,报了很多类型错误。 建议使用3.* 的版本。  4.1.1版本我注释掉了部分OpenCv的代码,算法可以继续无差运行,但总感觉不完美,所以我替换成了3.* 的版本。

 

关键信息

C++类型

我处理成的对应c#类型

cv::Point

System.Drawing.Point

vector<cv::Point>&

List<System.Drawing.Point>

vector<vector<cv::Point>>&

List<List<System.Drawing.Point>>

char*

string

struct labelPoint

public class labelPoint

Tuple

public class TupleInfo

Ftuple

public class FtupleInfo

int**

Int[,]

uchar*

Byte[]

iterator

更改为For循环

Scalar

Emgu: MCvScalar

 public class labelPoint
{
        public int x;
        public int y;
        public int label;
};

public class TupleInfo
{
        public double FR;
        public double FG;
        public double FB;

        public double BR;
        public double BG;
        public double BB;

        public double sigmaf;
        public double sigmab;
        public int flag;
};

public class FtupleInfo
{
        public double FR;
        public double FG;
        public double FB;

        public double BR;
        public double BG;
        public double BB;

        public double alphar;
        public double confidence;
};

public void loadImage(string sFile)
{
        pImg = CvInvoke.Imread(sFile);
        if (pImg.GetData() == null || pImg.GetData().Length == 0)
        {
              Console.WriteLine("load pImg failed!");
              return;
        }

        height = pImg.Rows;
        width = pImg.Cols;
        step = pImg.Step / (pImg.ElementSize / pImg.NumberOfChannels);
        channels = pImg.NumberOfChannels;
        data = pImg.GetData();
        unknownIndex = new int[height,width];
        tri = new int[height,width];
        alpha = new int[height,width];
}

有了这些对应信息,你就可以尝试自己移植了。

 

深化尝试

       从我的调试结果来看,可以实现抠图,如果你也同时在C++环境下运行了算法,你会发现C#环境下的算法运行时间远超C++。然后我就考虑将抠图算法在C++环境下打包成dll供C#调用。

       由于不熟悉c#与C++的交互,我踩了很多坑,实现的也并不算完美,不过总之调通了。

我将提前准备好的原图以及Trimap图的路径传给C++的dll,期望返回处理过后的Alpha数组。

c#端:

   首先添加我生成的C++ Dll并声明引用。

 [DllImport("ImgIntelligHelper.dll",CharSet = CharSet.Unicode)]
 public extern static IntPtr GetMatteMap([MarshalAs(UnmanagedType.LPStr)] string sInput,[MarshalAs(UnmanagedType.LPStr)] string sOutput);

然后对dll中的函数进行调用,返回透明度矩阵的内存地址,然后赋值到我创建的数组中。

// sInput - 原图路径; sTrimap: Trimap图路径
System.Drawing.Bitmap oBitmap = new Bitmap(sInput);
int nlength = oBitmap.Width * oBitmap.Height;
IntPtr intptr = GetMatteMap(sInput,sTrimap);
int[] arrAlpha = new int[nlength];
Marshal.Copy(intptr,arrAlpha,0,nlength);

C++端:

       新增了一个方法,将矩阵转换为int数组。

void AlphaMatting::GetAlphaMap()
{
    int h = matte.rows;
    int w = matte.cols;
    Map = new int[h*w];
    for (int i = 0; i < h; ++i)
    {
        for (int j = 0; j < w; ++j)
        {
            Map[i * w + j] = alpha[i][j];
        }
    };
}

// 接收图片并处理
int* GetMatteMap(char* sInput,char* sTrimap)
{
    AlphaMatting alphaMatHelper;
    alphaMatHelper.loadImage(sInput);
    alphaMatHelper.loadTrimap(sTrimap);
    alphaMatHelper.solveAlpha();
    alphaMatHelper.GetAlphaMap();
    return alphaMatHelper.Map;
}

最后用原图以及dll返回的Alpha数组实现抠图。 这是可行的方式,整个流程进行下来效率相对于纯C#版会有较大的改进。但是相对于纯C++版本来说,消耗还是过高。

然后我又尝试将C++版算法改成控制台应用程序。在C#中采用启动进程的方式,传入原图、Trimap图、输出图路径值,然后以不显示应用程序窗口的方式在后台静默执行。 进程结束后见到了C++应用程序处理过后的结果。

C#中调用C++生成的控制台应用exe。 

private void DoConvert(string sIndex)
{
    string sBasePath = AppDomain.CurrentDomain.BaseDirectory;
    string sExeFile = sBasePath + @"\AlphaMattingPlugin.exe";
    string sInput = sBasePath + @"\Datas\input" + sIndex+ ".jpg";
    string sTrimap = sBasePath + @"\Datas\trimap" + sIndex + ".jpg";
    string sOutput = sBasePath + @"\Datas\AlphaMattingPluginSample" + sIndex + ".png";

     Process process = new Process();
     process.StartInfo.FileName = sExeFile;
     // 调用C++版本的控制台Exe,传入原图、Trimap图、抠图结果输出文件路径
     process.StartInfo.Arguments = " " + sInput + " " + sTrimap + " " + sOutput;
     process.StartInfo.CreateNoWindow = false;
     process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
     process.Start();
     process.WaitForExit();
       
     if (File.Exists(sOutput))
         this.ShowImage(this.ImgResult,sOutput);
}

private void ShowImage(Image img,string sFile)
{
     byte[] bytes = System.IO.File.ReadAllBytes(sFile);
     BitmapImage bitImg = new BitmapImage();
     bitImg.BeginInit();
     bitImg.StreamSource = new System.IO.MemoryStream(bytes);
     bitImg.EndInit();
     bitImg.Freeze();
     img.Source = bitImg;
}

这样处理后比纯C++环境多耗时0.5s左右,这个结论对于我是能接受的。

采用这种方式,抠图算法执行时间消耗我进行了测试,如下图

分享图片

分享图片

?

原来需要10-20s的现在仅用1-3s就能实现。如下图在C#环境下WPF工程调用C++版exe的调试截图:

分享图片

?

分享图片

?

结论

        Alpha Matting抠图算法可以移植至C#平台,但是最佳实践还是用C++去处理,采用C#调用C++的方式会大大节省耗时。

        图片越大耗时会越高,目前我尚未尝试4K图。

        原本还想将Global Matting 及其他几种抠图算法也想法移植到C#平台,但是经过上文中一些列测试,发现还是保留原版本更为合理,用C#直接去调用Dll 或 包装的exe应用即可,而且效率更高。

        源码下载:微信扫描下方二维码文章末尾获取链接。

                                 

分享图片

?

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


项目中经常遇到CSV文件的读写需求,其中的难点主要是CSV文件的解析。本文会介绍CsvHelper、TextFieldParser、正则表达式三种解析CSV文件的方法,顺带也会介绍一下CSV文件的写方法。 CSV文件标准 在介绍CSV文件的读写方法前,我们需要了解一下CSV文件的格式。 文件示例 一
简介 本文的初衷是希望帮助那些有其它平台视觉算法开发经验的人能快速转入Halcon平台下,通过文中的示例开发者能快速了解一个Halcon项目开发的基本步骤,让开发者能把精力完全集中到算法的开发上面。 首先,你需要安装Halcon,HALCON 18.11.0.1的安装包会放在文章末尾。安装包分开发和
这篇文章主要简单记录一下C#项目的dll文件管理方法,以便后期使用。 设置dll路径 参考C#开发奇技淫巧三:把dll放在不同的目录让你的程序更整洁中间的 方法一:配置App.config文件的privatePath : &lt;runtime&gt; &lt;assemblyBinding xml
在C#中的使用JSON序列化及反序列化时,推荐使用Json.NET——NET的流行高性能JSON框架,当然也可以使用.NET自带的 System.Text.Json(.NET5)、DataContractJsonSerializer、JavaScriptSerializer(不推荐)。
事件总线是对发布-订阅模式的一种实现,是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。&#xA;EventBus维护一个事件的字典,发布者、订阅者在事件总线中获取事件实例并执行发布、订阅操作,事件实例负责维护、执行事件处理程序。
通用翻译API的HTTPS 地址为https://fanyi-api.baidu.com/api/trans/vip/translate,使用方法参考通用翻译API接入文档 。&#xA;请求方式可使用 GET 或 POST 方式(Content-Type 请指定为:application/x-www-for
词云”由美国西北大学新闻学副教授、新媒体专业主任里奇·戈登(Rich Gordon)于2006年最先使用,是通过形成“关键词云层”或“关键词渲染”,对文本中出现频率较高的“关键词”的视觉上的突出。词云图过滤掉大量的文本信息,使浏览者只要一眼扫过文本就可以领略文本的主旨。&#xA;网上大部分文章介绍的是使用P
微软在.NET中对串口通讯进行了封装,我们可以在.net2.0及以上版本开发时直接使用SerialPort类对串口进行读写操作。&#xA;为操作方便,本文对SerialPort类做了一些封装,暂时取名为**SerialPortClient**。
简介 管道为进程间通信提供了平台, 管道分为两种类型:匿名管道、命名管道,具体内容参考.NET 中的管道操作。简单来说,匿名管道只能用于本机的父子进程或线程之间,命名管道可用于远程主机或本地的任意两个进程,本文主要介绍命名管道的用法。 匿名管道在本地计算机上提供进程间通信。 与命名管道相比,虽然匿名
目录自定义日志类NLog版本的日志类Serilog版本的日志类 上个月换工作,新项目又要重新搭建基础框架,把日志实现部分单独记录下来方便以后参考。 自定义日志类 代码大部分使用ChatGPT生成,人工进行了测试和优化,主要特点: 线程安全,日志异步写入文件不影响业务逻辑 支持过期文件自动清理,也可自
[TOC] # 原理简介 本文参考[C#/WPF/WinForm/程序实现软件开机自动启动的两种常用方法](https://blog.csdn.net/weixin_42288432/article/details/120059296),将里面中的第一种方法做了封装成**AutoStart**类,使
简介 FTP是FileTransferProtocol(文件传输协议)的英文简称,而中文简称为“文传协议”。用于Internet上的控制文件的双向传输。同时,它也是一个应用程序(Application)。基于不同的操作系统有不同的FTP应用程序,而所有这些应用程序都遵守同一种协议以传输文件。 FTP
使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。 将特性与程序实体相关联后,可以在运行时使用反射这项技术查询特性。&#xA;在 C# 中,通过用方括号 ([]) 将特性名称括起来,并置于应用该特性的实体的声明上方以指定特性。
# 简介 主流的识别库主要有ZXing.NET和ZBar,OpenCV 4.0后加入了QR码检测和解码功能。本文使用的是ZBar,同等条件下ZBar识别率更高,图片和部分代码参考[在C#中使用ZBar识别条形码](https://www.cnblogs.com/w2206/p/7755656.htm
C#中Description特性主要用于枚举和属性,方法比较简单,记录一下以便后期使用。 扩展类DescriptionExtension代码如下: using System; using System.ComponentModel; using System.Reflection; /// &lt;
本文实现一个简单的配置类,原理比较简单,适用于一些小型项目。主要实现以下功能:保存配置到json文件、从文件或实例加载配置类的属性值、数据绑定到界面控件。&#xA;一般情况下,项目都会提供配置的设置界面,很少手动更改配置文件,所以选择以json文件保存配置数据。
前几天用SerialPort类写一个串口的测试程序,关闭串口的时候会让界面卡死。网上大多数方法都是定义2个bool类型的标记Listening和Closing,关闭串口和接受数据前先判断一下。我的方法是DataReceived事件处理程序用this.BeginInvoke()更新界面,不等待UI线程
约束告知编译器类型参数必须具备的功能。 在没有任何约束的情况下,类型参数可以是任何类型。 编译器只能假定 System.Object 的成员,它是任何 .NET 类型的最终基类。 如果客户端代码使用不满足约束的类型,编译器将发出错误。 通过使用 where 上下文关键字指定约束。&#xA;最常用的泛型约束为
protobuf-net是用于.NET代码的基于契约的序列化程序,它以Google设计的“protocol buffers”序列化格式写入数据,适用于大多数编写标准类型并可以使用属性的.NET语言。&#xA;protobuf-net可通过NuGet安装程序包,也可直接访问github下载源码:https:/
工作中经常遇到需要实现TCP客户端或服务端的时候,如果每次都自己写会很麻烦且无聊,使用SuperSocket库又太大了。这时候就可以使用SimpleTCP了,当然仅限于C#语言。&#xA;SimpleTCP是一个简单且非常有用的 .NET 库,用于处理启动和使用 TCP 套接字(客户端和服务器)的重复性任务