Fork me on GitHub

C# 海康多工业相机高频数据丢帧、异常、内存等问题处理

一、项目背景:

7台相机(通过网口、USB链接到同一台电脑),帧率 50+/s ,每炉次采集大概持续30s,单张图片大小(100kb和5mb),部分图片获取到之后还要进入一个dll的算子,该算子执行完成在40ms左右,在高频数据情况下如果处理慢会导致回调堵塞,炉次间隔2min左右。

 

二、项目最终目的:

要求绝对不能丢帧,支持图片内存计算和保存为BMP文件,不能卡顿闪退,程序正常7x24小时运行。

丢帧的原因:回调方法里面处理速度一定要快,也就是不能堵塞回调方法(比如数据直接塞队列,这时候就解耦了),否则的话回调堵塞,数据就会堆在缓存节点中(可以针对每个相机设置一定的缓存节点,代价就是需要消耗更多的内存),当缓存节点达到上限时就会导致丢帧,旧数据被新数据覆盖

 

三、硬件排查

目前测试运行的硬件配置

CPU:如果太差会导致算子计算时间太长,影响整体时间以及队列积压严重

内存:对象池初始化之后会在内存中开辟一定内存,以待下次使用;当消费速度慢的时候,大量数据会积压在队列中也需要占用大量内存

固态:需要Nvme高性能固态盘,因为设计到存图,如果IO操作慢也会导致消费速度慢,进而导致数据积压丢数据

image

 

四、其他基础设置

网口相机需要设置巨帧数据包、速度和双工

image

 

 

在MVS工具里面先对所有相机状态进行监测,确保硬件没有问题,同时MVS测试需要达到存图不丢数据,当这些准备工作做完就可以开始程序开发了

image

 五、程序开发

海康相机链接与回调事件(包含相机链接、初始化、指定相机位置、软触发、硬触发、以及初始化参数等功能)

using DevComponents.DotNetBar;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Dnn;
using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Media3D;
using System.Xml.Linq;
using WaveformStressPatterninSpection.Models;
using WaveformStressPatterninSpection.Models.Enums;
using WaveformStressPatterninSpection.Server.Helper;
using WaveformStressPatterninSpection.Server.HKCamera;
using WaveformStressPatterninSpection.Server.IServer;
using WaveformStressPatterninSpection.Server.Server;
using static WaveformStressPatterninSpection.Server.HKCamera.MyCamera;

namespace WaveformStressPatterninSpection.Server.Base
{
    public class HK_CameraHelper
    {
        private static readonly Lazy<HK_CameraHelper> _instance =
            new Lazy<HK_CameraHelper>(() => new HK_CameraHelper());

        // 提供全局访问点
        public static HK_CameraHelper Instance => _instance.Value;

        // 通过属性注入依赖(公共可写属性)
        public ICancheService _cancheService { get; set; }
        MyCamera.cbOutputExdelegate cbImage;

        int m_nDevNum;
        public MyCamera[] m_pMyCamera;
        MyCamera.MV_CC_DEVICE_INFO[] m_pDeviceInfo;
        MyCamera.MV_CC_DEVICE_INFO_LIST m_pDeviceList = new MyCamera.MV_CC_DEVICE_INFO_LIST();
        public bool m_bGrabbing = false;
        public MyCamera.MV_FRAME_OUT_INFO_EX[] m_stFrameInfo;
        private Object[] m_BufForSaveImageLock;
        int[] m_nFrames;
        public UInt32[] m_nSaveImageBufSize;
        public IntPtr[] m_pSaveImageBuf;
        public string savePathByDB = Path.Combine(Environment.CurrentDirectory, "productPic");
        public bool[] runStatus;
        public string[] snNo;
        public WriteLogFileHelper _writeLogFile = new WriteLogFileHelper();

        public readonly SimpleObjectPool<SaveImageDto> _objectPool = new SimpleObjectPool<SaveImageDto>(10000); 
        public ConcurrentDictionary<int, Channel<SaveImageDto>> _perCameraChannels = new();

        private HK_CameraHelper()
        {
            cbImage = new cbOutputExdelegate(ImageCallBack);
            _cancheService = ContainerLocator.Container.Resolve<ICancheService>();
        }

        [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
        public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);

        public string DeviceListAcq()
        {
            System.GC.Collect();
            int nRet = MyCamera.MV_CC_EnumDevices_NET(MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE, ref m_pDeviceList);
            m_nDevNum = CommonHelper.UsingCamera.Length;
            m_pMyCamera = new MyCamera[m_nDevNum];
            m_pDeviceInfo = new MyCamera.MV_CC_DEVICE_INFO[m_nDevNum];
            m_BufForSaveImageLock = new Object[m_nDevNum];
            m_nSaveImageBufSize = new UInt32[m_nDevNum];
            m_pSaveImageBuf = new IntPtr[m_nDevNum];
            m_stFrameInfo = new MyCamera.MV_FRAME_OUT_INFO_EX[m_nDevNum];
            m_nFrames = new int[m_nDevNum];
            runStatus = new bool[m_nDevNum];
            snNo = new string[m_nDevNum];

            if (m_nDevNum > 0)
            {
                for (int i = 0; i < m_nDevNum; ++i)
                {
                    m_BufForSaveImageLock[i] = new Object();
                    m_nFrames[i] = 0;
                    m_nSaveImageBufSize[i] = 0;
                    m_pSaveImageBuf[i] = IntPtr.Zero;
                    m_stFrameInfo[i] = new MyCamera.MV_FRAME_OUT_INFO_EX();
                    runStatus[i] = true;
                    snNo[i] = "";
                }

                return bnOpen_Click();
            }
            else
            {
                return "未发现相机,请检查相机是否正确连接或被其他设备占用";
            }
        }

        public string bnOpen_Click()
        {
            string resMes = "";
            List<CameraInfo> cbDeviceList = new List<CameraInfo>();
            List<CameraInfo> detectedCameras = new List<CameraInfo>();
            for (int j = 0; j < (int)m_pDeviceList.nDeviceNum; j++)
            {
                //ch:获取选择的设备信息 | en:Get Selected Device Information
                MyCamera.MV_CC_DEVICE_INFO device = (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(m_pDeviceList.pDeviceInfo[j], typeof(MyCamera.MV_CC_DEVICE_INFO))!;

                string cameraSn = "";
                string chManufacturerName = "";

                if (device.nTLayerType == MyCamera.MV_GIGE_DEVICE)
                {
                    MyCamera.MV_GIGE_DEVICE_INFO_EX gigeInfo = (MyCamera.MV_GIGE_DEVICE_INFO_EX)MyCamera.ByteToStruct(device.SpecialInfo.stGigEInfo, typeof(MyCamera.MV_GIGE_DEVICE_INFO_EX));
                    cameraSn = gigeInfo.chSerialNumber;
                    chManufacturerName = gigeInfo.chManufacturerName;
                }
                else if (device.nTLayerType == MyCamera.MV_USB_DEVICE)
                {
                    MyCamera.MV_USB3_DEVICE_INFO_EX usbInfo = (MyCamera.MV_USB3_DEVICE_INFO_EX)MyCamera.ByteToStruct(device.SpecialInfo.stUsb3VInfo, typeof(MyCamera.MV_USB3_DEVICE_INFO_EX));
                    cameraSn = usbInfo.chSerialNumber;
                    chManufacturerName = usbInfo.chManufacturerName;
                }

                detectedCameras.Add(new CameraInfo { snNo = cameraSn, device = device, chManufacturerName = chManufacturerName });
            }

            for (int i = 0; i < CommonHelper.UsingCamera.Length; i++)
            {
                //ch:打开设备 | en:Open Device
                CameraInfo info = new CameraInfo() { CanmeraId = i };
                var camera = detectedCameras.FirstOrDefault(x => x.snNo == CommonHelper.UsingCamera[i]);
                if (camera == null) continue;

                var device = camera.device;

                if (null == m_pMyCamera[i])
                {
                    m_pMyCamera[i] = new MyCamera();
                    if (null == m_pMyCamera[i])
                    {
                        resMes = "设备初始化失败";
                        continue;
                    }
                }

                int nRet = m_pMyCamera[i].MV_CC_CreateDevice_NET(ref device);
                if (MyCamera.MV_OK != nRet)
                {
                    resMes = "创建失败";
                    continue;
                }

                nRet = m_pMyCamera[i].MV_CC_OpenDevice_NET();
                if (MyCamera.MV_OK != nRet)
                {
                    resMes = "打开失败";
                    continue;
                }
                else
                {

                    m_pDeviceInfo[i] = camera.device;
                    // ch:探测网络最佳包大小(只对GigE相机有效) | en:Detection network optimal package size(It only works for the GigE camera)
                    if (camera.device.nTLayerType == MyCamera.MV_GIGE_DEVICE)
                    {
                        int nPacketSize = m_pMyCamera[i].MV_CC_GetOptimalPacketSize_NET();
                        if (nPacketSize > 0)
                        {
                            nRet = m_pMyCamera[i].MV_CC_SetIntValueEx_NET("GevSCPSPacketSize", nPacketSize);
                            if (nRet != MyCamera.MV_OK)
                            {
                                resMes = "最佳包大小设置失败";
                                continue;
                            }
                        }
                        else
                        {
                        }
                    }

                    nRet = m_pMyCamera[i].MV_CC_SetEnumValue_NET("TriggerMode", (uint)MyCamera.MV_CAM_TRIGGER_MODE.MV_TRIGGER_MODE_OFF);
                    uint nImageNodeNum = 400;

                    nRet = m_pMyCamera[i].MV_CC_SetImageNodeNum_NET(nImageNodeNum);
                    if (MV_OK != nRet)
                    {
                        resMes = "节点缓存设置失败";
                        continue;
                    }
                    nRet = m_pMyCamera[i].MV_CC_RegisterImageCallBackEx_NET(cbImage, (IntPtr)i);
                    if (MyCamera.MV_OK != nRet)
                    {
                        resMes = "回调方式设置失败";
                        continue;
                    }
                }

                //加载用户集1,某些情况下海康相机自身Bug导致丢帧,手动加载一下用户集就好了
                nRet = m_pMyCamera[i].MV_CC_SetEnumValue_NET("UserSetSelector", 1);
                if (MV_OK != nRet)
                {
                    resMes = $"{camera.snNo}用户集1加载失败";
                    continue;
                }
                nRet = m_pMyCamera[i].MV_CC_SetCommandValue_NET("UserSetLoad");
                if (MV_OK != nRet)
                {
                    resMes = $"{camera.snNo}用户集1加载失败";
                    continue;
                }

                MyCamera.MVCC_INTVALUE stParam = new MyCamera.MVCC_INTVALUE();
                MVCC_FLOATVALUE mVCC_FLOATVALUE = new MVCC_FLOATVALUE();

                m_pMyCamera[i].MV_CC_GetIntValue_NET("Width", ref stParam);
                info.Width = stParam.nCurValue + "";

                m_pMyCamera[i].MV_CC_GetIntValue_NET("Height", ref stParam);
                info.Height = stParam.nCurValue + "";

                m_pMyCamera[i].MV_CC_GetIntValue_NET("OffsetX", ref stParam);
                info.OffsetX = stParam.nCurValue + "";

                m_pMyCamera[i].MV_CC_GetIntValue_NET("OffsetY", ref stParam);
                info.OffsetY = stParam.nCurValue + "";

                m_pMyCamera[i].MV_CC_GetFloatValue_NET("Gain", ref mVCC_FLOATVALUE);
                info.Gain = mVCC_FLOATVALUE.fCurValue + "";

                m_pMyCamera[i].MV_CC_GetFloatValue_NET("ExposureTime", ref mVCC_FLOATVALUE);
                info.ExposureTime = mVCC_FLOATVALUE.fCurValue + "";

                snNo[i] = camera.snNo;
                info.CanmeraId = i;
                info.snNo = camera.snNo;
                info.chManufacturerName = camera.chManufacturerName;
                info.CanmeraName = "相机" + (1 + i).ToString();
                info.isOpenCamera = false;
                info.isStartCatchPic = true;
                info.device = device;
                info.device_Hik = m_pMyCamera[i];
                info.isOpenCamera = true;
                info.m_BufForSaveImageLock = new object();
                info.m_nFrames = new int();
                info.m_nSaveImageBufSize = 0;
                info.m_pSaveImageBuf = IntPtr.Zero;
                info.m_stFrameInfo = new MyCamera.MV_FRAME_OUT_INFO_EX();
                info.isWave = i > 1;
                info.isSoft = false;

                cbDeviceList.Add(info);
            }

            if (!string.IsNullOrEmpty(resMes))
                return resMes;

            if (cbDeviceList == null || cbDeviceList.Count < 1)
                return "未发现相机,请检查相机是否正确连接或被其他设备占用";

            _cancheService.UpdateCameraSettingByCache(cbDeviceList); //更新缓存中的相机信息
            m_bGrabbing = true;

            LineTrigger();

            for (int i = 0; i < m_pMyCamera.Length; i++)
            {
                int cameraIndex = i;
                var camera = m_pMyCamera[i];
                var nRet = camera.MV_CC_StartGrabbing_NET();

                if (nRet != MyCamera.MV_OK)
                {
                    return "相机打开失败";
                }

                CommonHelper.CreateDirectoryIfNotExists(Path.Combine(Environment.CurrentDirectory, "TempPic"));
                CommonHelper.CreateDirectoryIfNotExists(Path.Combine(Environment.CurrentDirectory, "BackTempPic")); //执行失败后的备份文件
            }

            //todo 完善实际数据数据库然后再放出来
            //var cameraListByDb = _cancheService.GetCameraList();  
            //if (cameraListByDb != null && cameraListByDb.Count > 0)
            //{
            //    foreach (var cameraInfo in cameraListByDb)
            //    {
            //        SettingParameter(cameraInfo);     //参数下发
            //    }
            //}

            return "";
        }

        /// <summary>
        /// 工业相机回调方法
        /// </summary>
        /// <param name="pData"></param>
        /// <param name="pFrameInfo"></param>
        /// <param name="pUser"></param>
        public void ImageCallBack(IntPtr pData, ref MyCamera.MV_FRAME_OUT_INFO_EX pFrameInfo, IntPtr pUser)
        {
            if (pData == IntPtr.Zero || pFrameInfo.nFrameLen <= 0)
            {
                _writeLogFile.WriteLog("Error",
                    $"无效帧:Camera={pUser.ToInt64()}, pData={pData.ToInt64()}, Length={pFrameInfo.nFrameLen}");
                return;
            }
            int cameraIndex = (int)pUser;
            if (snNo == null || snNo.Any(c => string.IsNullOrEmpty(c))) return;

            long frameId = Interlocked.Increment(ref CommonHelper.CamearaIncrementNum[cameraIndex]);

            //应力斑或者波形识别过程中不保存图片,一般都是识别完成他们要手动踩出来一节,导致生成图片从1开始会替换之前生成的图片,导致图片被占用
            if (CommonHelper.WaveStatus == OngoingState.Loading || CommonHelper.StressStatus == OngoingState.Loading)
            {
                //同时重置计数
                CommonHelper.CamearaIncrementNum = new long[CommonHelper.UsingCamera.Length];
                return;
            }

            var dto = _objectPool.Take();
            try
            {
                using var tempMat = new Mat(pFrameInfo.nHeight, pFrameInfo.nWidth, DepthType.Cv8U, 1, pData, pFrameInfo.nWidth);
                var cloned = tempMat.Clone();

                dto.MatInfo = cloned;
                dto.frameId = frameId;
                dto.nIndex = cameraIndex;
                dto.snNo = snNo[cameraIndex];

                if (_perCameraChannels.TryGetValue(cameraIndex, out var channel))
                {
                    if (!channel.Writer.TryWrite(dto))
                    {
                        dto.Dispose();
                        dto.Reset();
                        _objectPool.Return(dto);
                        _writeLogFile.WriteLog("Error", $"相机{cameraIndex} 队列已满,丢弃帧{frameId}");
                    }
                }
            }
            catch (Exception ex)
            {
                _writeLogFile.WriteLog("Error", $"相机 {cameraIndex} 处理失败:{ex.Message}");
                // 确保释放资源
                if (dto != null)
                {
                    dto.Dispose();
                    dto.Reset();
                    _objectPool.Return(dto);
                }
            }
        }

        /// <summary>
        /// 关闭摄像头
        /// </summary>
        /// <returns></returns>
        public string bnClose_Click()
        {
            var resMsg = "";

            for (int i = 0; i < m_nDevNum; ++i)
            {
                try
                {
                    int nRet;
                    if (m_pMyCamera[i] != null)
                    {
                        nRet = m_pMyCamera[i].MV_CC_CloseDevice_NET();
                        if (MyCamera.MV_OK != nRet)
                        {
                            resMsg = "相机关闭失败";
                            continue;
                        }

                        nRet = m_pMyCamera[i].MV_CC_DestroyDevice_NET();
                        if (MyCamera.MV_OK != nRet)
                        {
                            resMsg = "相机关闭失败";
                            continue;
                        }

                        if (m_pSaveImageBuf[i] != IntPtr.Zero)
                        {
                            Marshal.FreeHGlobal(m_pSaveImageBuf[i]);
                            m_pSaveImageBuf[i] = IntPtr.Zero;
                        }
                    }
                }
                catch (Exception ex)
                {
                    continue;
                }
            }

            // ch:取流标志位清零 | en:Zero setting grabbing flag bit
            m_bGrabbing = false;

            return resMsg;
        }

        /// <summary>
        /// 硬触发
        /// </summary>
        /// <returns></returns>
        public void LineTrigger()
        {
            for (int i = 0; i < m_nDevNum; ++i)
            {
                lock (m_BufForSaveImageLock[i])
                {
                    var nRet = m_pMyCamera[i].MV_CC_SetEnumValue_NET("TriggerMode", (uint)MyCamera.MV_CAM_TRIGGER_MODE.MV_TRIGGER_MODE_ON);
                    nRet = m_pMyCamera[i].MV_CC_SetEnumValue_NET("TriggerSource", (uint)MyCamera.MV_CAM_TRIGGER_SOURCE.MV_TRIGGER_SOURCE_LINE0);
                }
            }
        }

        /// <summary>
        /// 软触发
        /// </summary>
        /// <param name="device_Hik"></param>
        /// <returns></returns>
        public string SoftTrigger(MyCamera device_Hik)
        {
            int nRet;
            int i = Array.IndexOf(m_pMyCamera, device_Hik);
            if (i == -1) return "";

            m_pMyCamera[i].MV_CC_SetEnumValue_NET("TriggerMode", (uint)MyCamera.MV_CAM_TRIGGER_MODE.MV_TRIGGER_MODE_ON);
            m_pMyCamera[i].MV_CC_SetEnumValue_NET("TriggerSource", (uint)MyCamera.MV_CAM_TRIGGER_SOURCE.MV_TRIGGER_SOURCE_SOFTWARE);
            nRet = m_pMyCamera[i].MV_CC_SetCommandValue_NET("TriggerSoftware");
            if (MyCamera.MV_OK != nRet)
            {
                return "";
            }

            MyCamera.MV_FRAME_OUT_INFO_EX pFrameInfo = new MyCamera.MV_FRAME_OUT_INFO_EX();
            MyCamera.MV_FRAME_OUT stFrameInfo = new MyCamera.MV_FRAME_OUT();

            nRet = m_pMyCamera[i].MV_CC_GetImageBuffer_NET(ref stFrameInfo, 1000);
            pFrameInfo = stFrameInfo.stFrameInfo;
            var pData = stFrameInfo.pBufAddr;

            if (nRet != MyCamera.MV_OK)
            {
                _cancheService.UpdateLogging(new Logging() { Content = string.Format($"{snNo[i]}_软触发抓图失败,硬触发进行中!"), StartTime = DateTime.Now });
                return "";
            }

            var picUrl = StartCatchPic(i, m_pMyCamera[i], pFrameInfo, pData);

            // 必须释放缓冲区!
            m_pMyCamera[i].MV_CC_FreeImageBuffer_NET(ref stFrameInfo);
            //保存

            m_pMyCamera[i].MV_CC_SetEnumValue_NET("TriggerSource", (uint)MyCamera.MV_CAM_TRIGGER_SOURCE.MV_TRIGGER_SOURCE_LINE0);
            return picUrl;
        }

        /// <summary>
        /// 软触发抓图
        /// </summary>
        public string StartCatchPic(int nIndex, MyCamera m_MyCameraInfo, MyCamera.MV_FRAME_OUT_INFO_EX pFrameInfo, IntPtr pData = default)
        {
            if (m_MyCameraInfo == null) return "";
            int nRet = 0;

            if (m_pSaveImageBuf[nIndex] == IntPtr.Zero || pFrameInfo.nFrameLen > m_nSaveImageBufSize[nIndex])
            {
                if (m_pSaveImageBuf[nIndex] != IntPtr.Zero)
                {
                    Marshal.Release(m_pSaveImageBuf[nIndex]);
                    m_pSaveImageBuf[nIndex] = IntPtr.Zero;
                }

                m_pSaveImageBuf[nIndex] = Marshal.AllocHGlobal((Int32)pFrameInfo.nFrameLen);
                if (m_pSaveImageBuf[nIndex] == IntPtr.Zero)
                {
                    return "";
                }
                m_nSaveImageBufSize[nIndex] = pFrameInfo.nFrameLen;
            }

            m_stFrameInfo[nIndex] = pFrameInfo;
            CopyMemory(m_pSaveImageBuf[nIndex], pData, pFrameInfo.nFrameLen);

            if (m_stFrameInfo[nIndex].nFrameLen != 0)
            {
                var curTime = DateTime.Now;
                var fileName = string.Format($"{snNo[nIndex]}_Dev_ti{m_stFrameInfo[nIndex].nHostTimeStamp}.jpeg");
                if (!Directory.Exists(savePathByDB))
                {
                    Directory.CreateDirectory(savePathByDB);
                }

                MyCamera.MV_SAVE_IMG_TO_FILE_PARAM stSaveFileParam = new MyCamera.MV_SAVE_IMG_TO_FILE_PARAM();
                stSaveFileParam.enImageType = MyCamera.MV_SAVE_IAMGE_TYPE.MV_Image_Jpeg;
                stSaveFileParam.enPixelType = m_stFrameInfo[nIndex].enPixelType;
                stSaveFileParam.pData = m_pSaveImageBuf[nIndex];
                stSaveFileParam.nDataLen = m_stFrameInfo[nIndex].nFrameLen;
                stSaveFileParam.nHeight = m_stFrameInfo[nIndex].nHeight;
                stSaveFileParam.nWidth = m_stFrameInfo[nIndex].nWidth;
                stSaveFileParam.nQuality = 80;
                stSaveFileParam.iMethodValue = 2;
                stSaveFileParam.pImagePath = Path.Combine(savePathByDB, fileName);
                nRet = m_MyCameraInfo.MV_CC_SaveImageToFile_NET(ref stSaveFileParam);
                if (MyCamera.MV_OK != nRet)
                {
                    _cancheService.UpdateLogging(new Logging() { Content = string.Format($"{snNo[nIndex]}_软触发失败"), StartTime = DateTime.Now });
                    return "";
                }
                else
                {
                    //先清除字典中的无效数据
                    //_cancheService.ClearInVainData();

                    //插入玻璃信息
                    var fileNameByDb = string.Format($"pack://siteoforigin:,,,/productPic/{fileName}");
                    //var temperName = _cancheService.GetCurMaxTemperNo();

                    //var temperNo = _cancheService.GetCurMaxTemperNo();

                    //var insertRes = _cancheService.UpdateTemperingHistory(new TemperingHistory()
                    //{
                    //    TemperName = temperName,
                    //    TemperNo = temperNo,
                    //    ImageSource = fileNameByDb,
                    //    StartTime = curTime,
                    //    Num = 1,
                    //    Status = true,
                    //    GlassSize = "",
                    //});

                    ////插入日志
                    //if (insertRes)
                    //{
                    //_cancheService.UpdateLogging(new Logging() { Content = string.Format($"{temperName}成像完成"), StartTime = curTime });
                    //}

                    return fileNameByDb;
                }
            }

            return "";
        }

        /// <summary>
        /// 更新摄像头状态
        /// </summary>
        public void UpdateCameraStatu()
        {
            for (int i = 0; i < m_nDevNum; i++)
            {
                try
                {
                    runStatus[i] = m_pMyCamera[i]?.MV_CC_IsDeviceConnected_NET() ?? false;
                }
                catch { runStatus[i] = false; }
            }
        }

        /// <summary>
        /// 参数下发
        /// </summary>
        public string SettingParameter(CameraSetting cameraByDb)
        {
            var cameraInfoByCache = _cancheService.GetCameraListByCache().FirstOrDefault(x => x.snNo == cameraByDb.SnNo);
            if (cameraInfoByCache == null)
            {
                _cancheService.UpdateLogging(new Logging() { Content = string.Format($"{cameraByDb.SnNo} 参数下发失败"), StartTime = DateTime.Now });
                return "参数下发失败";
            }

            int nRet;
            nRet = cameraInfoByCache.device_Hik.MV_CC_StopGrabbing_NET();
            if (nRet != MyCamera.MV_OK)
            {
                return "参数下发失败";
            }

            nRet = cameraInfoByCache.device_Hik.MV_CC_SetFloatValue_NET("ExposureTime", float.Parse(cameraByDb.ExposureTime));
            if (nRet != MyCamera.MV_OK)
            {
                _cancheService.UpdateLogging(new Logging() { Content = string.Format($"{cameraByDb.SnNo} 曝光时间参数下发失败"), StartTime = DateTime.Now });
                return "曝光时间参数下发失败";
            }

            nRet = cameraInfoByCache.device_Hik.MV_CC_SetFloatValue_NET("Gain", float.Parse(cameraByDb.Gain.ToString()));
            if (nRet != MyCamera.MV_OK)
            {
                _cancheService.UpdateLogging(new Logging() { Content = string.Format($"{cameraByDb.SnNo} 增益参数下发失败"), StartTime = DateTime.Now });
                return "增益参数下发失败";
            }

            nRet = cameraInfoByCache.device_Hik.MV_CC_SetWidth_NET(uint.Parse(cameraByDb.Width.ToString()));
            nRet = cameraInfoByCache.device_Hik.MV_CC_SetHeight_NET(uint.Parse(cameraByDb.Height.ToString()));
            nRet = cameraInfoByCache.device_Hik.MV_CC_SetIntValue_NET("OffsetX", uint.Parse(cameraByDb.OffsetX));
            nRet = cameraInfoByCache.device_Hik.MV_CC_SetIntValue_NET("OffsetY", uint.Parse(cameraByDb.OffsetY.ToString()));

            if (0 != nRet)
            {
                _cancheService.UpdateLogging(new Logging() { Content = string.Format($"{cameraByDb.SnNo} ROI参数下发失败"), StartTime = DateTime.Now });
                return "ROI参数下发失败";
            }

            nRet = cameraInfoByCache.device_Hik.MV_CC_StartGrabbing_NET();
            if (nRet != MyCamera.MV_OK)
            {
                return "程序启动失败,请关闭程序重新启动";
            }

            return "";
        }
    }
}

  

队列消费端(数据获取消费、部分相机进入算子计算、部分相机进行裁剪、存图等)

using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Util;
using Newtonsoft.Json;
using System.Collections.Concurrent;
using System.Configuration;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Text;
using System.Threading.Channels;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using WaveformStressPatterninSpection.Models;
using WaveformStressPatterninSpection.Models.Enums;
using WaveformStressPatterninSpection.Server.Base;
using WaveformStressPatterninSpection.Server.CommonDto;
using WaveformStressPatterninSpection.Server.Helper;
using WaveformStressPatterninSpection.Server.IServer;
using WaveformStressPatterninSpection.Server.ThermalImaging.DTOs;
using static WaveformStressPatterninSpection.Server.HKCamera.MyCamera;
using Size = System.Drawing.Size;

namespace WaveformStressPatterninSpection.Server.HKCamera
{
    /// <summary>
    /// 识别处理
    /// </summary>
    public class CameraDataProcessor
    {
        // 单例实例(带参数)
        private static readonly Lazy<CameraDataProcessor> _instance;

        // 静态构造函数初始化单例(需要先设置依赖)
        static CameraDataProcessor()
        {
            // 初始时实例为 null,需通过依赖注入
            _instance = new Lazy<CameraDataProcessor>(() =>
                new CameraDataProcessor(HK_CameraHelper.Instance));
        }

        // 公共访问点
        public static CameraDataProcessor Instance => _instance.Value;

        //相机操作字段
        private readonly SimpleObjectPool<SaveImageDto> _objectPool;
        private readonly HK_CameraHelper _callbackHandler;
        private ConcurrentDictionary<int, Channel<SaveImageDto>> _perCameraChannels = new();
        private WriteLogFileHelper writeLogFileHelper = new WriteLogFileHelper();
        private readonly IEventAggregator _eventAggregator;
        private readonly static string pyScriptPath = Path.Combine(Environment.CurrentDirectory, "stressgather.py");    //应力斑识别py脚本
        private readonly static string pyCmdPath = ConfigurationManager.AppSettings["PyCmdPath"]!;
        private readonly bool isProduct = bool.Parse(ConfigurationManager.AppSettings["IsProduct"]!);   //是否是生产环境
        private readonly TemperNoManager _temperNoManager;
        private readonly ICancheService _cancheService;

        private CameraDataProcessor(HK_CameraHelper callbackHandler)
        {
            _callbackHandler = callbackHandler;
            _objectPool = callbackHandler._objectPool;
            _perCameraChannels = callbackHandler._perCameraChannels;
            _eventAggregator = ContainerLocator.Container.Resolve<IEventAggregator>();
            _temperNoManager = ContainerLocator.Container.Resolve<TemperNoManager>();
            _cancheService = ContainerLocator.Container.Resolve<ICancheService>();

            InitializeConsumers();
            InitialzeWaveParameter();
        }

        #region 图片存储整合方案
        private void InitializeConsumers()
        {
            foreach (var cameraId in Enumerable.Range(0, CommonHelper.UsingCamera.Length))
            {
                _perCameraChannels.GetOrAdd(cameraId,
                    _ => Channel.CreateBounded<SaveImageDto>(new BoundedChannelOptions(3000)
                    {
                        SingleReader = true,
                        SingleWriter = true,
                        FullMode = BoundedChannelFullMode.DropWrite
                    }));
            }

            foreach (var cameraId in Enumerable.Range(0, CommonHelper.UsingCamera.Length))
            {
                // 再启动消费线程(用专属Thread而非Task,才能绑核)
                var thread = new Thread(() => ConsumeCamera(cameraId))
                {
                    IsBackground = true,
                    Name = $"CamConsumer_{cameraId}",
                    Priority = ThreadPriority.AboveNormal
                };
                thread.Start();
            }
        }

        private void ConsumeCamera(int cameraId)
        {
            //QuickCoreBinder.BindThreadToCore(cameraId);
            var reader = _perCameraChannels[cameraId].Reader;

            while (true)
            {
                SaveImageDto item = null;
                try
                {
                    var waitTask = reader.WaitToReadAsync().AsTask();
                    if (!waitTask.ConfigureAwait(false).GetAwaiter().GetResult()) break;

                    while (reader.TryRead(out item))
                    {
                        ImageSaveOrCacheProcess(item);
                        item.Dispose();
                        item.Reset();
                        _objectPool.Return(item);
                        item = null;
                    }
                }
                catch (Exception ex)
                {
                    writeLogFileHelper.WriteLog("Error.log", $"消费者{cameraId}异常:{ex.Message}");
                }
                finally
                {
                    if (item != null)
                    {
                        item.Dispose();
                        item.Reset();
                        _objectPool.Return(item);
                    }
                }
            }
        }

        /// <summary>
        /// 图片保存或直接缓存处理
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        private void ImageSaveOrCacheProcess(SaveImageDto item)
        {
            if (CommonHelper._curTemperingInfo == null || string.IsNullOrEmpty(CommonHelper._curTemperingInfo.TemperNo))
            {
                writeLogFileHelper.WriteLog("炉次信息", $"相机{item.nIndex} frameId={item.frameId} 无炉次信息,跳过");
                return;
            }

            string filePath = Path.Combine(Environment.CurrentDirectory, "TempPic", CommonHelper._curTemperingInfo.TemperNo, $"{item.snNo}", $"{item.frameId:D6}.bmp");

            try
            {
                Debug.WriteLine($"开始创建图片:{item.snNo}__{item.frameId}");

                if (item.nIndex < 2)
                {
                    var chopValue = item.nIndex == 0 ? CommonHelper.LeftChop : CommonHelper.RightChop;
                    using var fullMat = item.MatInfo;

                    Rectangle rectInfo = new Rectangle(0, chopValue, fullMat.Width, CommonHelper.SliderLenght);
                    using Mat chopMat = new Mat(fullMat, rectInfo);

                    int newWidth = (int)(chopMat.Width * CommonHelper.ScaleRatio);
                    int newHeight = (int)(chopMat.Height * CommonHelper.ScaleRatio);
                    using Mat dstMat = new Mat();
                    CvInvoke.Resize(chopMat, dstMat, new Size(newWidth, newHeight), 0, 0, Inter.Area);
                    CvInvoke.Imwrite(filePath, dstMat);

                    Interlocked.Increment(ref CommonHelper.CamearaIncrementNumByEnd[item.nIndex]);
                }
                else
                {
                    if (CommonHelper.IsUseMemoryModeByWaveRecog)    //内存模式走队列
                    {
                        //CvInvoke.Imwrite(filePath, item.MatInfo);
                        ProcessCamera(item.MatInfo, item.nIndex, (int)item.frameId - 1);     //-1是因为上面已经自增了,后面数组下标是从0开始的
                    }
                    else
                    {
                        CvInvoke.Imwrite(filePath, item.MatInfo);
                    }
                }
            }
            catch (Exception ex)
            {
                writeLogFileHelper.WriteLog("Error", $"错误:{ex.Message}__{CommonHelper._curTemperingInfo.TemperNo}__{item.snNo}__{item.frameId}");
            }
        }
        #endregion

        /// <summary>
        /// 执行py脚本应力斑识别
        /// </summary>
        public async Task RunPyScriptByStree(string[] inputFileName)
        {
            
        }

        /// <summary>
        /// 执行C的dll波形识别
        /// </summary>
        /// <returns></returns>
        public async Task RunCScriptByWaveform(string[] inputFileName)
        {
            await Task.Run(() => Calculate(inputFileName));
        }

        /// <summary>
        /// 初始化波形参数
        /// </summary>
        public void InitialzeWaveParameter()
        {
           
        }

        /// <summary>
        /// 取到所有图片开始计算
        /// </summary>
        /// <param name="inputFileName"></param>
        private async Task Calculate(string[] inputFileName)
        {
            
        }

        /// <summary>
        /// 处理单张图像的函数,图片产生直接存缓存,等所有图片都处理完了再进行整体计算
        /// </summary>
        /// <param name="src">数据源</param>
        /// <param name="cameraIndex">相机索引</param>
        /// <param name="i"></param>
        public void ProcessCamera(Mat src, int cameraIndex, int i)
        {
            
        }
    }
}

  

六、碰到的问题以及解决方式:

1、丢帧(通过MV_CC_SetImageNodeNum_NET增加节点、增加硬盘读写速率等)

2、内存飙升(无法使用Marshal.Copy内存拷贝,因为数据量大根本顶不住、只能依靠硬盘高速写入从而更快的释放指针,如果数据都存内存肯定顶不住,所以只能存文件)

3、成像异常(时间错乱)

4、内存稳定增长不下去(我这里是WPF程序,前端显示的图片比较大,每次打开没有回收,导致使用内存一直增长,每次打开新图的时候先将他释放再给新数据)

posted @ 2026-03-20 15:13  SmallChan  阅读(3)  评论(0)    收藏  举报