折腾笔记[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);
            }
        }
    }
}
posted @ 2025-11-09 19:14  qsBye  阅读(0)  评论(0)    收藏  举报