內容簡介
將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工程項目
避免采坑
- 最好不要用最新版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 |
深化嘗試
從我的調試結果來看,可以實現摳圖,如果你也同時在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應用即可,而且效率更高。
源碼下載:微信掃描下方二維碼文章末尾獲取鏈接。