C# 海康多工业相机高频数据丢帧、异常、内存等问题处理
一、项目背景:
7台相机(通过网口、USB链接到同一台电脑),帧率 50+/s ,每炉次采集大概持续30s,单张图片大小(100kb和5mb),部分图片获取到之后还要进入一个dll的算子,该算子执行完成在40ms左右,在高频数据情况下如果处理慢会导致回调堵塞,炉次间隔2min左右。
二、项目最终目的:
要求绝对不能丢帧,支持图片内存计算和保存为BMP文件,不能卡顿闪退,程序正常7x24小时运行。
丢帧的原因:回调方法里面处理速度一定要快,也就是不能堵塞回调方法(比如数据直接塞队列,这时候就解耦了),否则的话回调堵塞,数据就会堆在缓存节点中(可以针对每个相机设置一定的缓存节点,代价就是需要消耗更多的内存),当缓存节点达到上限时就会导致丢帧,旧数据被新数据覆盖
三、硬件排查
目前测试运行的硬件配置
CPU:如果太差会导致算子计算时间太长,影响整体时间以及队列积压严重
内存:对象池初始化之后会在内存中开辟一定内存,以待下次使用;当消费速度慢的时候,大量数据会积压在队列中也需要占用大量内存
固态:需要Nvme高性能固态盘,因为设计到存图,如果IO操作慢也会导致消费速度慢,进而导致数据积压丢数据

四、其他基础设置
网口相机需要设置巨帧数据包、速度和双工

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

五、程序开发
海康相机链接与回调事件(包含相机链接、初始化、指定相机位置、软触发、硬触发、以及初始化参数等功能)
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程序,前端显示的图片比较大,每次打开没有回收,导致使用内存一直增长,每次打开新图的时候先将他释放再给新数据)

浙公网安备 33010602011771号