折腾笔记[36]-调用海康SDK实现相机拍照
摘要
使用c#调用海康SDK实现相机拍照.
前言
本文目的是分享人工踩坑经验, AI搜索引擎可以更快给出正确结果(用于投喂AI😂).
关键信息
- 海康SDK版本: 4.6.x
- .net8框架
- 封装: HikVisionCom
- 测试序列号:"00DA4347368"
- dll文件位置1: C:\Program Files (x86)\Common Files\MVS\Runtime\Win64_x64\MvCameraControl.dll
- dll文件位置2: C:\Program Files (x86)\MVS\Applications\Win64
- kernel32.dll(用Everything.exe软件搜索)
实现
文件
PS W:\Lab\exp42-hikvision-conn\CamTest\libs> tree.com /f
文件夹 PATH 列表
卷序列号为 099D-E17A
W:.
│ Grab_Callback.exe
│ GrabImage.exe
│ kernel32.dll
│ libmmd.dll
│ Microsoft.VC90.CRT.manifest
│ Microsoft.VC90.DebugCRT.manifest
│ msvcm90.dll
│ msvcp90.dll
│ msvcr90.dll
│ msvcr120.dll
│ MVAIndustryControl.dll
│ MVARenderWidget.dll
│ MVCameraSDK.csproj
│ MVCameraSDK.sln
│ MVCameraSDK.snk
│ MVCameraSDK_net8.csproj
│ MvCamLVision.dll
│ MvDSS.ax
│ MvDSS2.ax
│ MVFGControl.dll
│ MvFGProducerCML.cti
│ MvFGProducerCXP.cti
│ MvFGProducerGEV.cti
│ MvFGProducerXoF.cti
│ MvFrameGrabberControl.dll
│ MVGigEVisionSDK.dll
│ MvISPControl.dll
│ MvLCProducer.dll
│ MVMemAlloc.dll
│ MvProducerGEV.cti
│ MvProducerU3V.cti
│ MvProducerVIR.dll
│ MvRender.dll
│ MvSDKVersion.dll
│ MvSerial.dll
│ MvSerialCtrl.dll
│ MvUsb3vTL.dll
│ NetworkAdapterCfgLib.dll
│ NetworkAdapterCfgLib_NIC.dll
│ ParametrizeCamera_AreaScanIOSettings.exe
│ ParametrizeCamera_LineScanIOSettings.exe
│ qwt.dll
│ ScreenPoint.dll
│ BasicDemo.exe
│ CommonTools.dll
│ ConvertPixelType.exe
│ CustomCtr.dll
│ DeviceEnumMng.dll
│ MvCameraControl.Net.dll
│ MvCameraControl.Net.xml
│ MvCameraControl.dll
│ CommonParameters.ini
│ D3DCompiler_43.dll
│ d3dcompiler_47.dll
│ d3dx9_43.dll
│ FormatConversion.dll
│ GCBase_MD_VC120_v3_0_MV.dll
│ GenApi_MD_VC120_v3_0_MV.dll
│ libusb0.dll
│ Log_MD_VC120_v3_0_MV.dll
│ log4cpp_MD_VC120_v3_0_MV.dll
│ MathParser_MD_VC120_v3_0_MV.dll
│ MediaProcess.dll
│ msvcp120.dll
│ msvcr100.dll
│ MvCameraControlGUI.dll
│ MvCameraControlWrapper.dll
│ MvCameraPatch.dll
│ NodeMapData_MD_VC120_v3_0_MV.dll
│ pthreadGC2.dll
│ pthreadVC2.dll
│ SuperRender.dll
│ svml_dispmd.dll
│ XmlParser_MD_VC120_v3_0_MV.dll
│ CLAllSerial_MD_VC120_v3_0_MV.dll
│ CLProtocol_MD_VC120_v3_0_MV.dll
│ CLSerCOM.dll
│ CLSerHvc.dll
│
└─ThirdParty
avutil-57.dll
libwinpthread-1.dll
swscale-6.dll
官方示例
# 创建C#工程,在工程中将MVCAM_COMMON_RUNENV\DotNet\AnyCpu路径下的MvCamCtrl.Net.dIl添加到工程引用中,MVCAM_COMMON_RUNENV即为PC环境变量
echo $env:MVCAM_COMMON_RUNENV
$env:MVCAM_COMMON_RUNENV
dotnet new classlib -n HikVisionCom -f net8.0
cd HikVisionCom
# 也可以通过nuget包方式直接添加引用
dotnet add package MvCameraControl.Net
tree.com /f
官方示例(枚举设备、连接设备、获取图像):
GrabImage.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MvCamCtrl.NET;
using System.Runtime.InteropServices;
using System.IO;
using System.Threading;
namespace GrabImage
{
class GrabImage
{
static bool g_bExit = false;
public static void ReceiveImageWorkThread(object obj)
{
int nRet = MyCamera.MV_OK;
MyCamera device = obj as MyCamera;
MyCamera.MV_FRAME_OUT stImageOut = new MyCamera.MV_FRAME_OUT();
while (true)
{
nRet = device.MV_CC_GetImageBuffer_NET(ref stImageOut, 1000);
if (nRet == MyCamera.MV_OK)
{
Console.WriteLine("Get Image Buffer:" + "Width[" + Convert.ToString(stImageOut.stFrameInfo.nWidth) + "] , Height[" + Convert.ToString(stImageOut.stFrameInfo.nHeight)
+ "] , FrameNum[" + Convert.ToString(stImageOut.stFrameInfo.nFrameNum) + "]");
device.MV_CC_FreeImageBuffer_NET(ref stImageOut);
}
else
{
Console.WriteLine("Get Image failed:{0:x8}", nRet);
}
if (g_bExit)
{
break;
}
}
}
static void Main(string[] args)
{
int nRet = MyCamera.MV_OK;
// ch: 初始化 SDK | en: Initialize SDK
MyCamera.MV_CC_Initialize_NET();
MyCamera device = new MyCamera();
do
{
// ch:枚举设备 | en:Enum device
MyCamera.MV_CC_DEVICE_INFO_LIST stDevList = new MyCamera.MV_CC_DEVICE_INFO_LIST();
nRet = MyCamera.MV_CC_EnumDevices_NET(MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE, ref stDevList);
if (MyCamera.MV_OK != nRet)
{
Console.WriteLine("Enum device failed:{0:x8}", nRet);
break;
}
Console.WriteLine("Enum device count : " + Convert.ToString(stDevList.nDeviceNum));
if (0 == stDevList.nDeviceNum)
{
break;
}
MyCamera.MV_CC_DEVICE_INFO stDevInfo; // 通用设备信息
// ch:打印设备信息 en:Print device info
for (Int32 i = 0; i < stDevList.nDeviceNum; i++)
{
stDevInfo = (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(stDevList.pDeviceInfo[i], typeof(MyCamera.MV_CC_DEVICE_INFO));
if (MyCamera.MV_GIGE_DEVICE == stDevInfo.nTLayerType)
{
MyCamera.MV_GIGE_DEVICE_INFO_EX stGigEDeviceInfo = (MyCamera.MV_GIGE_DEVICE_INFO_EX)MyCamera.ByteToStruct(stDevInfo.SpecialInfo.stGigEInfo, typeof(MyCamera.MV_GIGE_DEVICE_INFO_EX));
uint nIp1 = ((stGigEDeviceInfo.nCurrentIp & 0xff000000) >> 24);
uint nIp2 = ((stGigEDeviceInfo.nCurrentIp & 0x00ff0000) >> 16);
uint nIp3 = ((stGigEDeviceInfo.nCurrentIp & 0x0000ff00) >> 8);
uint nIp4 = (stGigEDeviceInfo.nCurrentIp & 0x000000ff);
Console.WriteLine("[device " + i.ToString() + "]:");
Console.WriteLine("DevIP:" + nIp1 + "." + nIp2 + "." + nIp3 + "." + nIp4);
Console.WriteLine("ModelName:" + stGigEDeviceInfo.chModelName + "\n");
}
else if (MyCamera.MV_USB_DEVICE == stDevInfo.nTLayerType)
{
MyCamera.MV_USB3_DEVICE_INFO_EX stUsb3DeviceInfo = (MyCamera.MV_USB3_DEVICE_INFO_EX)MyCamera.ByteToStruct(stDevInfo.SpecialInfo.stUsb3VInfo, typeof(MyCamera.MV_USB3_DEVICE_INFO_EX));
Console.WriteLine("[device " + i.ToString() + "]:");
Console.WriteLine("SerialNumber:" + stUsb3DeviceInfo.chSerialNumber);
Console.WriteLine("ModelName:" + stUsb3DeviceInfo.chModelName + "\n");
}
}
Int32 nDevIndex = 0;
Console.Write("Please input index(0-{0:d}):", stDevList.nDeviceNum - 1);
try
{
nDevIndex = Convert.ToInt32(Console.ReadLine());
}
catch
{
Console.Write("Invalid Input!\n");
break;
}
if (nDevIndex > stDevList.nDeviceNum - 1 || nDevIndex < 0)
{
Console.Write("Input Error!\n");
break;
}
stDevInfo = (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(stDevList.pDeviceInfo[nDevIndex], typeof(MyCamera.MV_CC_DEVICE_INFO));
// ch:创建设备 | en:Create device
nRet = device.MV_CC_CreateDevice_NET(ref stDevInfo);
if (MyCamera.MV_OK != nRet)
{
Console.WriteLine("Create device failed:{0:x8}", nRet);
break;
}
// ch:打开设备 | en:Open device
nRet = device.MV_CC_OpenDevice_NET();
if (MyCamera.MV_OK != nRet)
{
Console.WriteLine("Open device failed:{0:x8}", nRet);
break;
}
// ch:探测网络最佳包大小(只对GigE相机有效) | en:Detection network optimal package size(It only works for the GigE camera)
if (stDevInfo.nTLayerType == MyCamera.MV_GIGE_DEVICE)
{
int nPacketSize = device.MV_CC_GetOptimalPacketSize_NET();
if (nPacketSize > 0)
{
nRet = device.MV_CC_SetIntValueEx_NET("GevSCPSPacketSize", nPacketSize);
if (nRet != MyCamera.MV_OK)
{
Console.WriteLine("Warning: Set Packet Size failed {0:x8}", nRet);
}
}
else
{
Console.WriteLine("Warning: Get Packet Size failed {0:x8}", nPacketSize);
}
}
// ch:设置触发模式为off || en:set trigger mode as off
if (MyCamera.MV_OK != device.MV_CC_SetEnumValue_NET("TriggerMode", 0))
{
Console.WriteLine("Set TriggerMode failed:{0:x8}", nRet);
break;
}
// ch:开启抓图 | en:start grab
nRet = device.MV_CC_StartGrabbing_NET();
if (MyCamera.MV_OK != nRet)
{
Console.WriteLine("Start grabbing failed:{0:x8}", nRet);
break;
}
Thread hReceiveImageThreadHandle = new Thread(ReceiveImageWorkThread);
hReceiveImageThreadHandle.Start(device);
Console.WriteLine("Press enter to exit");
Console.ReadKey();
g_bExit = true;
Thread.Sleep(1000);
// ch:停止抓图 | en:Stop grab image
nRet = device.MV_CC_StopGrabbing_NET();
if (MyCamera.MV_OK != nRet)
{
Console.WriteLine("Stop grabbing failed:{0:x8}", nRet);
break;
}
// ch:关闭设备 | en:Close device
nRet = device.MV_CC_CloseDevice_NET();
if (MyCamera.MV_OK != nRet)
{
Console.WriteLine("Close device failed:{0:x8}", nRet);
break;
}
// ch:销毁设备 | en:Destroy device
nRet = device.MV_CC_DestroyDevice_NET();
if (MyCamera.MV_OK != nRet)
{
Console.WriteLine("Destroy device failed:{0:x8}", nRet);
break;
}
} while (false);
if (MyCamera.MV_OK != nRet)
{
// ch:销毁设备 | en:Destroy device
nRet = device.MV_CC_DestroyDevice_NET();
if (MyCamera.MV_OK != nRet)
{
Console.WriteLine("Destroy device failed:{0:x8}", nRet);
}
}
// ch: 反初始化SDK | en: Finalize SDK
MyCamera.MV_CC_Finalize_NET();
Console.WriteLine("Press enter to exit");
Console.ReadKey();
}
}
}
封装
配置:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- <OutputType>Exe</OutputType> -->
<OutputType>Library</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<!-- 目标框架 -->
<TargetFramework>net8.0</TargetFramework>
<!-- 对外 COM 可见 -->
<ComVisible>true</ComVisible>
<EnableComHosting>true</EnableComHosting>
<EnableRegFreeCom>true</EnableRegFreeCom>
<!-- 注册 COM 时用的 GUID(可选) -->
<Guid>b2c3d4e5-f6a7-4b5c-8d9e-0f1a2b3c4d5e</Guid>
<!-- 关键:允许不安全代码 -->
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- 让输出目录始终为 bin\<Configuration>\net8.0\ -->
<AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
<ItemGroup>
<Reference Include="System.Drawing.Common">
<HintPath>libs\System.Drawing.Common.dll</HintPath>
</Reference>
</ItemGroup>
<!-- 把 libs 里所有 dll 拷到输出目录 -->
<Target Name="CopyLibs" AfterTargets="Build">
<ItemGroup>
<NativeLibs Include="$(MSBuildProjectDirectory)\libs\*" />
</ItemGroup>
<Copy SourceFiles="@(NativeLibs)" DestinationFolder="$(TargetDir)" SkipUnchangedFiles="true" OverwriteReadOnlyFiles="true" />
</Target>
</Project>
封装代码:
// 文件: HikVisionCom.cs
// 功能: 对外COM接口
// 标准库
using System;
// using System.Drawing.Common;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
// 海康库
using MvCamCtrl.NET;
namespace HikVisionCom
{
[ComVisible(true)]
[Guid("EAA0C3F7-8D07-4EF1-B7A4-CE31F1A7DC2E")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IHikCamera
{
void Init(string serial);
void Connect();
void Disconnect();
string Shot(int exposure = 13000, float gain = 1f);
void StartStream(int exposure = 13000, float gain = 1f);
void StopStream();
bool IsStreaming { get; }
}
[ComVisible(true)]
[Guid("0B3C3297-0D91-4A98-9C52-BB9E3CE4728F")]
[ClassInterface(ClassInterfaceType.None)]
public class HikCamera : IHikCamera
{
private MyCamera _cam = new();
private MyCamera.MV_CC_DEVICE_INFO _info;
private bool _open;
private bool _grab;
private Task _streamTask;
private CancellationTokenSource _cts;
private readonly object _lock = new();
public void Init(string serial)
{
MyCamera.MV_CC_Initialize_NET();
var list = new MyCamera.MV_CC_DEVICE_INFO_LIST();
int ret = MyCamera.MV_CC_EnumDevices_NET(MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE, ref list);
if (ret != MyCamera.MV_OK) throw new COMException("Enum fail", ret);
for (int i = 0; i < list.nDeviceNum; i++)
{
var info = (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(list.pDeviceInfo[i],
typeof(MyCamera.MV_CC_DEVICE_INFO));
string sn = GetSerial(info);
if (sn.Equals(serial, StringComparison.OrdinalIgnoreCase))
{
_info = info;
return;
}
}
throw new COMException("Serial not found", -1);
}
public void Connect()
{
lock (_lock)
{
if (_open) return;
int ret = _cam.MV_CC_CreateDevice_NET(ref _info);
if (ret != MyCamera.MV_OK) throw new COMException("Create fail", ret);
ret = _cam.MV_CC_OpenDevice_NET();
if (ret != MyCamera.MV_OK) throw new COMException("Open fail", ret);
if (_info.nTLayerType == MyCamera.MV_GIGE_DEVICE)
{
int pkt = _cam.MV_CC_GetOptimalPacketSize_NET();
if (pkt > 0) _cam.MV_CC_SetIntValueEx_NET("GevSCPSPacketSize", pkt);
}
_open = true;
}
}
public void Disconnect()
{
StopStream();
lock (_lock)
{
if (!_open) return;
_cam.MV_CC_CloseDevice_NET();
_cam.MV_CC_DestroyDevice_NET();
_open = false;
}
}
public string Shot(int exposure = 13000, float gain = 1.0f)
{
lock (_lock)
{
if (!_open) throw new COMException("Not connected", -1);
SetExposureGain(exposure, gain);
// 1. 连续模式 + 开始抓图
_cam.MV_CC_SetEnumValue_NET("TriggerMode", 0);
int ret = _cam.MV_CC_StartGrabbing_NET();
if (ret != MyCamera.MV_OK) throw new COMException("StartGrabbing fail", ret);
// 2. 取一帧
var frame = new MyCamera.MV_FRAME_OUT();
ret = _cam.MV_CC_GetImageBuffer_NET(ref frame, 5000);
if (ret != MyCamera.MV_OK)
{
_cam.MV_CC_StopGrabbing_NET();
throw new COMException("Grab fail", ret);
}
string base64 = ToJpegBase64(ref frame);
_cam.MV_CC_FreeImageBuffer_NET(ref frame);
// 3. 停止抓图
_cam.MV_CC_StopGrabbing_NET();
return base64;
}
}
public bool IsStreaming { get; private set; }
public void StartStream(int exposure = 13000, float gain = 1f)
{
lock (_lock)
{
if (IsStreaming) return;
if (!_open) throw new COMException("Not connected", -1);
SetExposureGain(exposure, gain);
_cam.MV_CC_SetEnumValue_NET("TriggerMode", 0);
int ret = _cam.MV_CC_StartGrabbing_NET();
if (ret != MyCamera.MV_OK) throw new COMException("Start grab fail", ret);
IsStreaming = true;
_cts = new CancellationTokenSource();
_streamTask = Task.Run(StreamLoop, _cts.Token);
}
}
public void StopStream()
{
lock (_lock)
{
if (!IsStreaming) return;
_cts.Cancel();
_streamTask.Wait();
_cam.MV_CC_StopGrabbing_NET();
IsStreaming = false;
}
}
/* 事件:每次出新图触发,COM 客户端可订阅 */
[ComVisible(true)]
[Guid("EAA0C3F7-8D07-4EF1-B7A4-CE31F1A7DC2F")]
public delegate void NewImageDelegate([MarshalAs(UnmanagedType.BStr)] string base64Jpeg);
public event NewImageDelegate NewImage;
private void StreamLoop()
{
var frame = new MyCamera.MV_FRAME_OUT();
while (!_cts.Token.IsCancellationRequested)
{
int ret = _cam.MV_CC_GetImageBuffer_NET(ref frame, 1000);
if (ret == MyCamera.MV_OK)
{
string b64 = ToJpegBase64(ref frame);
_cam.MV_CC_FreeImageBuffer_NET(ref frame);
NewImage?.Invoke(b64);
}
}
}
/* ---------- helper ---------- */
private static string GetSerial(MyCamera.MV_CC_DEVICE_INFO info)
{
if (info.nTLayerType == MyCamera.MV_GIGE_DEVICE)
{
var gige = (MyCamera.MV_GIGE_DEVICE_INFO_EX)MyCamera.ByteToStruct(
info.SpecialInfo.stGigEInfo, typeof(MyCamera.MV_GIGE_DEVICE_INFO_EX));
return gige.chSerialNumber;
}
else
{
var usb = (MyCamera.MV_USB3_DEVICE_INFO_EX)MyCamera.ByteToStruct(
info.SpecialInfo.stUsb3VInfo, typeof(MyCamera.MV_USB3_DEVICE_INFO_EX));
return usb.chSerialNumber;
}
}
private void SetExposureGain(int exp, float gain)
{
_cam.MV_CC_SetEnumValue_NET("ExposureAuto", 0);
_cam.MV_CC_SetFloatValue_NET("ExposureTime", exp);
_cam.MV_CC_SetEnumValue_NET("GainAuto", 0);
_cam.MV_CC_SetFloatValue_NET("Gain", gain);
}
// 默认拍照使用灰度模式, 兼容几乎所有相机
private unsafe string ToJpegBase64(ref MyCamera.MV_FRAME_OUT frame)
{
int w = frame.stFrameInfo.nWidth;
int h = frame.stFrameInfo.nHeight;
int nFrameBufLen = w * h; // 手动计算缓冲区大小
using var bmp = new Bitmap(w, h, w, PixelFormat.Format8bppIndexed, (IntPtr)frame.pBufAddr);
// 设置灰度调色板
var pal = bmp.Palette;
for (int i = 0; i < 256; i++)
pal.Entries[i] = Color.FromArgb(i, i, i);
bmp.Palette = pal;
using var ms = new MemoryStream();
bmp.Save(ms, ImageFormat.Jpeg);
return Convert.ToBase64String(ms.ToArray());
}
}
}
注册:
管理员身份运行终端
regsvr32 "W:\Lab\exp42-hikvision-conn\Test\HikVision.comhost.dll"
测试代码(用法极简):
// Program.cs
// dotnet run 即可,会在工作目录生成 shot.jpg
// 需要项目文件 <Project Sdk="Microsoft.NET.Sdk"><PropertyGroup>
// <OutputType>Exe</OutputType><TargetFramework>net8.0-windows</TargetFramework>
// <PlatformTarget>x86</PlatformTarget><!-- 或 x64,与 COM 注册一致 -->
// </PropertyGroup></Project>
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace ComHikDemo
{
internal class Program
{
static void Main()
{
// 1. 创建 COM 对象
Type t = Type.GetTypeFromProgID("HikVisionCom.HikCamera");
if (t == null) throw new Exception("HikVisionCom.HikCamera 未注册");
dynamic cam = Activator.CreateInstance(t);
try
{
// 2. 初始化并连接(把下面串号换成你的相机序列号)
string serial = "00DA4347368";
cam.Init(serial);
cam.Connect();
// 3. 单帧采集,返回 Base64
// string b64 = cam.Shot(13000, 1.0f);
string b64 = cam.Shot();
// 4. 解码保存
byte[] jpeg = Convert.FromBase64String(b64);
File.WriteAllBytes("shot.jpg", jpeg);
Console.WriteLine("已保存 shot.jpg");
}
finally
{
// 5. 断开并释放
cam.Disconnect();
Marshal.ReleaseComObject(cam);
}
}
}
}

浙公网安备 33010602011771号