摘要
使用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
│
└─ThirdPartyavutil-57.dlllibwinpthread-1.dllswscale-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.Nettree.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 SDKMyCamera.MV_CC_Initialize_NET();MyCamera device = new MyCamera();do{// ch:枚举设备 | en:Enum deviceMyCamera.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 infofor (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 devicenRet = device.MV_CC_CreateDevice_NET(ref stDevInfo);if (MyCamera.MV_OK != nRet){Console.WriteLine("Create device failed:{0:x8}", nRet);break;}// ch:打开设备 | en:Open devicenRet = 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 offif (MyCamera.MV_OK != device.MV_CC_SetEnumValue_NET("TriggerMode", 0)){Console.WriteLine("Set TriggerMode failed:{0:x8}", nRet);break;}// ch:开启抓图 | en:start grabnRet = 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 imagenRet = device.MV_CC_StopGrabbing_NET();if (MyCamera.MV_OK != nRet){Console.WriteLine("Stop grabbing failed:{0:x8}", nRet);break;}// ch:关闭设备 | en:Close devicenRet = device.MV_CC_CloseDevice_NET();if (MyCamera.MV_OK != nRet){Console.WriteLine("Close device failed:{0:x8}", nRet);break;}// ch:销毁设备 | en:Destroy devicenRet = 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 devicenRet = device.MV_CC_DestroyDevice_NET();if (MyCamera.MV_OK != nRet){Console.WriteLine("Destroy device failed:{0:x8}", nRet);}}// ch: 反初始化SDK | en: Finalize SDKMyCamera.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);}}}
}