opencvsharp 是 opencv的c#版本,近期有项目使用了opencvsharp来进行图像处理。这个github上星级很高的项目果然是不错的,运行起来比较稳定,没有出现大的问题。但opencvsharp中没有cuda的完整支持,只有最基本的类型支持,无任何算法支持,想用就只能靠自己添加了。作者的解释如下:
大概就是说cuda需要用户自己编译opencv ,没有一个统一版本的dll提供使用,所以就删除了cuda的支持
其实也对,cuda的使用涉及cuda版本,使用的显卡算力等。使用c++版本的opencv时,也是要自己编译的。
但c#上使用就没有办法了吗?还好作者已经打好了基础,提供了GpuMat的支持,并有大量cup版本的函数进行参考。添加起来还是比较容易的。
项目目录 https://github.com/shimat/opencvsharp
编写步骤:
1.vc++编译带cuda的opencv
编译带cuda的opencv方法比较简单,网上随便搜一大堆,就不特别说明了。但我为了调试opencvsharp快一点,所有并没有像opencvsharp作者一样,编译出静态库,而是编译成共享库。这样反复编译,调试c#版时可以快一点。
2.将编译好的头文件和lib文件导入项目
我在opencvsharp4.1 版本的基础上修改代码的,其默认的头文件和库文件目录在 opencv_files_410 文件夹。我将里面的老文件全部删除,替换为自己编译好的文件。
打开opencvsharp项目,修改OpenCvSharpExtern 的包含目录与库目录
由于我用的是共享库,所以其附加依赖项也要修改下,并添加cuda相关的库依赖
3.启用cuda
opencvsharp默认是没有启用cuda的,需要修改 OpenCvSharp.csproj 文件添加 ENABLED_CUDA 和 修改OpenCvSharpExtern 添加预处理定义 ENABLED_CUDA 。
4.添加cuda函数
下面开始关键步骤,添加一个cuda函数。添加一个函数一般需要在4个地方添加代码。以添加一个 cuda.pyrUp(InputArray src, OutputArray dst, Stream& stream = Stream::Null()) 为例
1)在OpenCvSharpExtern 这个c++项目中添加一个c#方便调用的接口函数。为方便函数的管理。我重新建了个头文件来添加函数 文件名 cuda_warping.h
#ifndef _CPP_GPU_WARPING_H_
#define _CPP_GPU_WARPING_H_
#ifdef ENABLED_CUDA
#include "include_opencv.h"
using namespace cv::cuda;
CVAPI(void) cuda_imgproc_pyrUp(cv::_InputArray *src, cv::_OutputArray *dst, Stream* stream)
{
cv::cuda::pyrUp(*src, *dst, *stream);
}
#endif
#endif
由于添加了一个新头文件,所以 cuda.cpp 也要改一下,将新加的头文件包含进去
// ReSharper disable CppUnusedIncludeDirective
#include "cuda.h"
#include "cuda_GpuMat.h"
#include "cuda_warping.h"
2)在OpenCvSharp项目中,添加导入c++接口的函数。同样为了方便管理,我在PInvoke/cuda下重新建了一个NativeMethods_cuda_warping.cs文件
#if ENABLED_CUDA
using System;
using System.Runtime.InteropServices;
#pragma warning disable 1591
namespace OpenCvSharp {
// ReSharper disable InconsistentNaming
public static partial class NativeMethods {
[DllImport(DllExtern, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void cuda_imgproc_pyrUp(IntPtr src, IntPtr dst, IntPtr stream);
}
}
#endif
3)添加c#类,编写c#调用的函数,既最终使用的函数。同样我在Modules/cuda建了个新类 cuda_warping.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace OpenCvSharp.Cuda {
/// <summary>
/// GPU warping
/// </summary>
public static partial class cuda {
/// <summary>
/// GPU pyrUp
/// </summary>
public static void pyrUp(InputArray src, OutputArray dst, Stream stream = null) {
if (src == null)
throw new ArgumentNullException(nameof(src));
if (dst == null)
throw new ArgumentNullException(nameof(dst));
src.ThrowIfDisposed();
dst.ThrowIfNotReady();
NativeMethods.cuda_imgproc_pyrUp(src.CvPtr, dst.CvPtr, stream?.CvPtr ?? Stream.Null.CvPtr);
GC.KeepAlive(src);
GC.KeepAlive(dst);
dst.Fix();
}
}
}
代码修改参考了cpu版本的pyrup函数。基本就是照抄,改个函数的名字。
4)测试
改完后要测试一下函数能否正常运行,opencvsharp里正好有测试代码,那就照搬吧~~。在OpenCvSharp.Tests 里新建一个测试 GPUTest.cs
using System;
using Xunit;
using Xunit.Abstractions;
namespace OpenCvSharp.Tests {
// ReSharper disable InconsistentNaming
public class GPUTest : TestBase {
public GPUTest(ITestOutputHelper output) : base(output) {
}
[Fact]
public void SimpleGPUTest() {
Cuda.GpuMat gpumat = new Cuda.GpuMat();
Cuda.GpuMat gpumat_des = new Cuda.GpuMat();
Mat src = Image("lenna.png", ImreadModes.Grayscale);
gpumat.Upload(src);
Cuda.cuda.pyrUp(gpumat, gpumat_des);
Mat des = new Mat();
gpumat_des.Download(des);
Cv2.ImWrite("test.png",des);
}
}
}
注意,由于是共享dll,需要将动态库dll文件复制到测试项目的运行目录。我就简单的将一堆opencv_xxx410.dll 复制到了 test\OpenCvSharp.Tests\bin\Debug\netcoreapp2.0 文件夹下
效果
5.添加cuda类
cuda中一些运算是封装在类里面的,例如比较常用的canny算法。添加这些稍微复杂一些,但原理一样的,参考原作者cpu写的就好了~~
1)添加c#调用的c++接口。OpenCvSharpExtern项目新建头文件 cuda_imgproc.h 。我只简单实现了detect函数,其实CannyEdgeDetector还有其他设置参数,获取参数的方法,就不添加了,以免代码太复杂,影响理解,需要时可以再添加。总共有4个函数,一个新建,一个销毁,一个运行及detect,一个用来获取类的指针。
#ifndef _CPP_GPU_IMGPROC_H_
#define _CPP_GPU_IMGPROC_H_
#ifdef ENABLED_CUDA
#include "include_opencv.h"
using namespace cv::cuda;
CVAPI(cv::Ptr<CannyEdgeDetector>*) cuda_createCannyEdgeDetector(double low_thresh, double high_thresh, int apperture_size = 3, bool L2gradient = false)
{
cv::Ptr<CannyEdgeDetector> ptr = cv::cuda::createCannyEdgeDetector(low_thresh, high_thresh, apperture_size, L2gradient);
return new cv::Ptr<CannyEdgeDetector>(ptr);
}
CVAPI(void) cuda_CannyEdgeDetector_detect(CannyEdgeDetector *obj, cv::_InputArray *image, cv::_OutputArray *edges, Stream* stream)
{
obj->detect(*image, *edges, *stream);
}
CVAPI(void) cuda_Ptr_CannyEdgeDetector_delete(cv::Ptr<CannyEdgeDetector> *obj)
{
delete obj;
}
CVAPI(CannyEdgeDetector*) cuda_Ptr_CannyEdgeDetector_get(
cv::Ptr<CannyEdgeDetector> *ptr)
{
return ptr->get();
}
#endif
#endif
当然 cuda.cpp 里也要将这个头文件加进去
2)在OpenCvSharp项目中,添加导入c++接口的函数 。新建文件 NativeMethods_cuda_imgproc.cs
#if ENABLED_CUDA
using System;
using System.Runtime.InteropServices;
#pragma warning disable 1591
namespace OpenCvSharp {
// ReSharper disable InconsistentNaming
public static partial class NativeMethods {
[DllImport(DllExtern, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern IntPtr cuda_createCannyEdgeDetector(double low_thresh, double high_thresh, int apperture_size = 3, bool L2gradient = false);
[DllImport(DllExtern, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void cuda_CannyEdgeDetector_detect(IntPtr self, IntPtr image, IntPtr edges, IntPtr stream);
[DllImport(DllExtern, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void cuda_Ptr_CannyEdgeDetector_delete(IntPtr obj);
[DllImport(DllExtern, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern IntPtr cuda_Ptr_CannyEdgeDetector_get(IntPtr ptr);
}
}
#endif
3) 添加c# 类。新建文件 CannyEdgeDetector.cs
using System;
namespace OpenCvSharp.Cuda {
// ReSharper disable InconsistentNaming
/// <summary>
/// Creates implementation for cuda::CannyEdgeDetector
/// </summary>
public class CannyEdgeDetector : Algorithm {
/// <summary>
/// cv::Ptr<T>
/// </summary>
private Ptr objectPtr;
#region Init & Disposal
/// <summary>
///
/// </summary>
/// <param name="low_thresh"></param>
/// <param name="high_thresh"></param>
/// <param name="apperture_size"></param>
/// <param name="L2gradient"></param>
/// <returns></returns>
public static CannyEdgeDetector Create(
double low_thresh, double high_thresh, int apperture_size = 3, bool L2gradient = false) {
IntPtr ptr = NativeMethods.cuda_createCannyEdgeDetector(
low_thresh, high_thresh, apperture_size, L2gradient);
return new CannyEdgeDetector(ptr);
}
internal CannyEdgeDetector(IntPtr ptr) {
this.objectPtr = new Ptr(ptr);
this.ptr = objectPtr.Get();
}
/// <summary>
/// Releases managed resources
/// </summary>
protected override void DisposeManaged() {
objectPtr?.Dispose();
objectPtr = null;
base.DisposeManaged();
}
#endregion
/// <summary>
/// Finds edges in an image using the @cite Canny86 algorithm.
/// </summary>
/// <param name="image"></param>
/// <param name="edges"></param>
/// <param name="stream"></param>
public virtual void detect(InputArray image, OutputArray edges, Stream stream = null) {
if (image == null)
throw new ArgumentNullException(nameof(image));
if (edges == null)
throw new ArgumentNullException(nameof(edges));
image.ThrowIfDisposed();
edges.ThrowIfNotReady();
NativeMethods.cuda_CannyEdgeDetector_detect(ptr, image.CvPtr, edges.CvPtr, stream?.CvPtr ?? Stream.Null.CvPtr);
edges.Fix();
GC.KeepAlive(this);
GC.KeepAlive(image);
GC.KeepAlive(edges);
}
//#endregion
internal class Ptr : OpenCvSharp.Ptr {
public Ptr(IntPtr ptr) : base(ptr) {
}
public override IntPtr Get() {
var res = NativeMethods.cuda_Ptr_CannyEdgeDetector_get(ptr);
GC.KeepAlive(this);
return res;
}
protected override void DisposeUnmanaged() {
NativeMethods.cuda_Ptr_CannyEdgeDetector_delete(ptr);
base.DisposeUnmanaged();
}
}
}
}
代码基本就是参考复制 BackgroundSubtractorMOG.cs 这个类
4)新建测试代码 GPUTest.cs
using System;
using Xunit;
using Xunit.Abstractions;
namespace OpenCvSharp.Tests {
// ReSharper disable InconsistentNaming
public class GPUTest : TestBase {
public GPUTest(ITestOutputHelper output) : base(output) {
}
[Fact]
public void SimplecannyTest() {
Cuda.GpuMat gpumat = new Cuda.GpuMat();
Cuda.GpuMat gpumat_des = new Cuda.GpuMat();
Mat src = Image("lenna.png", ImreadModes.Grayscale);
gpumat.Upload(src);
Cuda.CannyEdgeDetector canny = Cuda.CannyEdgeDetector.Create(100, 50);
canny.detect(gpumat, gpumat_des);
Mat des = new Mat();
gpumat_des.Download(des);
Cv2.ImWrite("test.png", des);
}
}
}
效果
结语:上述代码都只是经过了简单的测试,没有经过实际运行环境的测试。长时间运行的稳定性没有测试,但代码主要是参考cpu部分的,问题应该不大。有问题可能会在gc 内存回收上吧~
博主你好,请问你是用哪个vs版本重新编译项目的,我用vs2019和vs2017都无法顺利编译,打开项目后项目的属性里都无法更改目标平台。报错如下:
错误 NETSDK1045 当前 .NET SDK 不支持将 .NET Standard 2.1 设置为目标。请将 .NET Standard 2.0 或更低版本设置为目标,或使用支持 .NET Standard 2.1 的 .NET SDK 版本。 OpenCvSharp.Blob C:\Program Files\dotnet\sdk\2.2.109\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.TargetFrameworkInference.targets 150
但我又无法重新设置,请问有遇到此情况吗
这个应该是你没有安装.NET Core 编译环境。如果你用的是vs2019,可以重新修改安装vs: 在”.net桌面开发”里 勾选”.net core 2.1/2.2″ 开发工具。安装完成后 可能还需要右击”解决方案” -> “还原 NuGet包”
目前使用vs2017,已经安装了net core2.1的。但还是无法修改目标框架
其中一个C++项目可以在属性里修改编译的框架 其他C#项目通过直接修改各项目的.csproj文件来实现 具体方法可以Google一下 这样可以删掉你不需要的框架 例如net core的
博主你好,读过您的文章,感觉您博学多识,让我获益匪浅
我遇到一个棘手的问题,opencv4.2的dnn已经支持CUDA后台加速,但opencvsharp4.2还没有支持。
我想在opencvsharp中dnn使用CUDA加速,应该怎么做?
恕我愚钝,期待您的回复
这个不难 需要重新编译OpenCVsharp 且不需要改代码 只需要改一些编译选项 我有项目已经成功使用了。简单说 先编译带cudnn的opencv( 需要同时安装CUDA 与 cudnn) 然后编译最新的opencvsharp。 编译方法与文中写的差不多 改改不报错就好了。可能中间有不少坑 Google下应该可以解决~
哇,被临幸了,好激动,
博主,我opencv已经编译OK,c++版本测试过dnn 可用cuda加速。
我基本上依照博文1,2两步,重新编译了opencvsharp,把生成的dll跟nuget下载的opencvsharp里面的同名dl做了替换,但使用的时候还是报错讲dnn.cpp没有build cuda。困扰了很多天。不知道哪里导致的。你有博文讲这部分吗?
可以加Q,当面赐教?万分感谢,感激涕零。
应该是你只替换了c#的dll,没有替换c++的dll。你可以尝试完全删除后,重新添加opencvsharp编译好的dll。除OpenCvSharp.dll等几个外 应该还有一个opencvsharpextensions 和 一堆opencv编译出来的opencvXX420名字的dll都放到程序根目录。总之nuget下的不要了,全换自己编译的。