做机器视觉开发的同学,大概率都经历过这种困境:每次接新项目,都要重复写一堆“无用功”——Halcon图像采集的初始化代码、参数配置的读写逻辑、结果可视化的绘制代码,甚至连异常处理的模板都要重新敲一遍。
之前接手一个电子元件缺陷检测项目时,前期花了一周时间搭基础框架,后期客户要求更换相机(从海康工业相机换成Basler),又得大面积修改采集模块代码;接着要加尺寸测量功能,图像处理模块的耦合度太高,改起来牵一发而动全身。痛定思痛:必须搞一个“通用框架”,把图像采集、处理、显示、配置这些通用功能封装起来,后续项目直接复用,只需要专注于具体的算法逻辑。
而C#的强类型特性、面向接口编程的灵活性,加上Halcon强大的机器视觉算法库,刚好是打造通用框架的绝佳组合。这篇文章就把整个框架的设计思路、源码实现、踩坑过程拆透——不是简单的API堆砌,而是从“解耦设计”到“落地优化”的完整实践,帮你少走90%的重复开发弯路。
halcondotnet.dll(默认路径:
C:Program FilesMVTec HALCON 20.11 Progressdotnet3564bit)Newtonsoft.Json:用于配置文件读写(NuGet直接安装)log4net:用于日志记录(NuGet直接安装)
辅助工具:Halcon HDevelop(算法调试、算子测试)、Postman(接口测试,可选)
halcondotnet.dll,务必设置“复制本地”为
True(否则运行时提示找不到dll)。目标平台:项目属性→生成→目标平台设为
x64(Halcon 64位版本不支持x86)。Halcon环境变量:安装Halcon后需重启VS,确保系统环境变量
HALCONROOT已配置(指向Halcon安装目录)。相机驱动:工业相机需安装对应驱动(海康MVS、Basler Pylon),否则采集模块无法识别设备。
通用框架的关键是“高内聚、低耦合”——把不同功能拆分成独立模块,通过接口通信,后续扩展时只需替换模块,无需修改整体架构。
视觉通用框架
├─ 配置管理模块:读写相机参数、算法参数、显示参数(JSON格式)
├─ 图像采集模块:支持工业相机、本地图片、视频流,抽象统一接口
├─ 图像处理模块:封装Halcon算法,支持算法动态切换、参数调整
├─ 结果可视化模块:集成Halcon HWindowControl,实现图像显示、ROI绘制、结果标注
└─ 日志与异常处理模块:记录运行日志、算法异常、设备故障,方便调试
ICapture、
IVisionProcess),具体实现类按需扩展(如
HikVisionCapture、
BaslerCapture)。依赖注入:通过接口注入实现类,降低模块间耦合(比如更换相机时,只需替换
ICapture的实现类)。配置外置:所有参数(相机IP、曝光时间、算法阈值)存入JSON文件,无需修改代码即可适配不同场景。可扩展:预留算法扩展接口、设备扩展接口,支持后期添加深度学习模型、新相机型号。
HObject、
HTuple)在C#中通过
halcondotnet.dll封装,可直接调用。图像流转:采集模块获取
HObject图像→传入处理模块执行Halcon算法→输出处理结果(如缺陷坐标、尺寸数据)→可视化模块显示
HObject和结果。资源释放:Halcon的
HObject需手动释放(
Dispose()),否则会导致内存泄漏,框架中统一封装释放逻辑。
配置模块负责读写所有参数,避免硬编码,核心是
ConfigManager类和JSON配置文件。
using System;
using Newtonsoft.Json;
namespace VisionFramework.Model
{
/// <summary>
/// 相机配置
/// </summary>
public class CameraConfig
{
[JsonProperty("CameraType")]
public string CameraType { get; set; } // 相机类型:HikVision/Basler/LocalImage
[JsonProperty("IpAddress")]
public string IpAddress { get; set; } // 相机IP(网口相机)
[JsonProperty("ExposureTime")]
public double ExposureTime { get; set; } // 曝光时间(μs)
[JsonProperty("Gain")]
public double Gain { get; set; } // 增益
[JsonProperty("ImagePath")]
public string ImagePath { get; set; } // 本地图片路径(本地图片模式)
}
/// <summary>
/// 算法配置
/// </summary>
public class AlgorithmConfig
{
[JsonProperty("ProcessType")]
public string ProcessType { get; set; } // 处理类型:DefectDetection/SizeMeasurement/BarcodeRead
[JsonProperty("Threshold")]
public int Threshold { get; set; } // 二值化阈值
[JsonProperty("MinDefectArea")]
public double MinDefectArea { get; set; } // 最小缺陷面积(像素)
[JsonProperty("TemplatePath")]
public string TemplatePath { get; set; } // 模板路径(模板匹配时)
}
/// <summary>
/// 全局配置
/// </summary>
public class GlobalConfig
{
[JsonProperty("CameraConfig")]
public CameraConfig CameraConfig { get; set; }
[JsonProperty("AlgorithmConfig")]
public AlgorithmConfig AlgorithmConfig { get; set; }
[JsonProperty("LogPath")]
public string LogPath { get; set; } // 日志路径
}
}
using System;
using System.IO;
using Newtonsoft.Json;
using VisionFramework.Model;
using log4net;
namespace VisionFramework.Config
{
public class ConfigManager
{
private static readonly ILog _log = LogManager.GetLogger(typeof(ConfigManager));
private static GlobalConfig _globalConfig;
private const string ConfigPath = "Config/global_config.json"; // 配置文件路径
/// <summary>
/// 加载配置
/// </summary>
/// <returns></returns>
public static GlobalConfig LoadConfig()
{
try
{
if (!File.Exists(ConfigPath))
{
// 配置文件不存在,创建默认配置
_globalConfig = CreateDefaultConfig();
SaveConfig(_globalConfig);
_log.Info("配置文件不存在,已创建默认配置");
return _globalConfig;
}
string json = File.ReadAllText(ConfigPath);
_globalConfig = JsonConvert.DeserializeObject<GlobalConfig>(json);
_log.Info("配置加载成功");
return _globalConfig;
}
catch (Exception ex)
{
_log.Error($"配置加载失败:{ex.Message}");
throw;
}
}
/// <summary>
/// 保存配置
/// </summary>
public static void SaveConfig(GlobalConfig config)
{
try
{
if (!Directory.Exists("Config"))
Directory.CreateDirectory("Config");
string json = JsonConvert.SerializeObject(config, Formatting.Indented);
File.WriteAllText(ConfigPath, json);
_log.Info("配置保存成功");
}
catch (Exception ex)
{
_log.Error($"配置保存失败:{ex.Message}");
throw;
}
}
/// <summary>
/// 创建默认配置
/// </summary>
private static GlobalConfig CreateDefaultConfig()
{
return new GlobalConfig
{
CameraConfig = new CameraConfig
{
CameraType = "HikVision",
IpAddress = "192.168.1.100",
ExposureTime = 1000,
Gain = 1.0,
ImagePath = "Images/test.jpg"
},
AlgorithmConfig = new AlgorithmConfig
{
ProcessType = "DefectDetection",
Threshold = 128,
MinDefectArea = 50.0,
TemplatePath = "Templates/template.shm"
},
LogPath = "Logs/"
};
}
}
}
采集模块是框架的“输入源”,支持工业相机和本地图片,通过
ICapture接口统一调用。
using System;
using HalconDotNet;
using VisionFramework.Model;
namespace VisionFramework.Capture
{
/// <summary>
/// 图像采集接口
/// </summary>
public interface ICapture : IDisposable
{
/// <summary>
/// 初始化采集设备
/// </summary>
/// <param name="config">相机配置</param>
/// <returns>是否成功</returns>
bool Init(CameraConfig config);
/// <summary>
/// 采集单帧图像
/// </summary>
/// <returns>采集到的HObject图像</returns>
HObject CaptureSingleFrame();
/// <summary>
/// 开始连续采集
/// </summary>
/// <param name="onFrameCaptured">图像采集完成回调</param>
void StartContinuousCapture(Action<HObject> onFrameCaptured);
/// <summary>
/// 停止连续采集
/// </summary>
void StopContinuousCapture();
/// <summary>
/// 设置相机参数(曝光时间、增益等)
/// </summary>
bool SetCameraParam(string paramName, double value);
}
}
using System;
using System.Threading;
using HalconDotNet;
using VisionFramework.Model;
using VisionFramework.Config;
using log4net;
// 需引用海康MVS SDK的dll:MvCameraControl.dll
using MvCamCtrl.NET;
namespace VisionFramework.Capture.Implement
{
public class HikVisionCapture : ICapture
{
private static readonly ILog _log = LogManager.GetLogger(typeof(HikVisionCapture));
private IntPtr _cameraHandle = IntPtr.Zero; // 相机句柄
private bool _isContinuousCapture = false;
private Thread _captureThread;
private Action<HObject> _frameCallback;
public bool Init(CameraConfig config)
{
try
{
// 1. 枚举设备
MyCamera.MV_CC_DEVICE_INFO_LIST deviceList = new MyCamera.MV_CC_DEVICE_INFO_LIST();
int ret = MyCamera.MV_CC_EnumDevices_NET(MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE, ref deviceList);
if (ret != 0)
throw new Exception($"枚举相机失败,错误码:{ret}");
// 2. 查找目标IP的相机
int deviceIndex = -1;
for (int i = 0; i < deviceList.nDeviceNum; i++)
{
MyCamera.MV_CC_DEVICE_INFO deviceInfo = deviceList.pDeviceInfo[i];
string ip = GetIpFromDeviceInfo(deviceInfo);
if (ip == config.IpAddress)
{
deviceIndex = i;
break;
}
}
if (deviceIndex == -1)
throw new Exception($"未找到IP为{config.IpAddress}的海康相机");
// 3. 创建相机句柄
_cameraHandle = MyCamera.MV_CC_CreateDevice_NET(ref deviceList.pDeviceInfo[deviceIndex]);
if (_cameraHandle == IntPtr.Zero)
throw new Exception("创建相机句柄失败");
// 4. 打开相机
ret = MyCamera.MV_CC_OpenDevice_NET(_cameraHandle);
if (ret != 0)
throw new Exception($"打开相机失败,错误码:{ret}");
// 5. 设置相机参数(曝光时间、增益)
SetCameraParam("ExposureTime", config.ExposureTime);
SetCameraParam("Gain", config.Gain);
_log.Info("海康相机初始化成功");
return true;
}
catch (Exception ex)
{
_log.Error($"海康相机初始化失败:{ex.Message}");
Dispose();
return false;
}
}
public HObject CaptureSingleFrame()
{
HObject image = null;
try
{
if (_cameraHandle == IntPtr.Zero)
throw new Exception("相机未初始化");
// 1. 开始采集
int ret = MyCamera.MV_CC_StartGrabbing_NET(_cameraHandle);
if (ret != 0)
throw new Exception($"开始采集失败,错误码:{ret}");
// 2. 获取图像数据
MyCamera.MV_FRAME_OUT_INFO_EX frameInfo = new MyCamera.MV_FRAME_OUT_INFO_EX();
IntPtr pData = Marshal.AllocHGlobal((int)frameInfo.nBufSize);
ret = MyCamera.MV_CC_GetImageBuffer_NET(_cameraHandle, ref frameInfo, pData, 1000);
if (ret != 0)
throw new Exception($"获取图像失败,错误码:{ret}");
// 3. 转换为Halcon的HObject
image = ConvertToHObject(pData, frameInfo);
// 4. 释放图像缓冲区
MyCamera.MV_CC_FreeImageBuffer_NET(_cameraHandle, pData, ref frameInfo);
MyCamera.MV_CC_StopGrabbing_NET(_cameraHandle);
return image;
}
catch (Exception ex)
{
_log.Error($"单帧采集失败:{ex.Message}");
image?.Dispose();
return null;
}
}
public void StartContinuousCapture(Action<HObject> onFrameCaptured)
{
if (_isContinuousCapture) return;
_frameCallback = onFrameCaptured;
_isContinuousCapture = true;
_captureThread = new Thread(ContinuousCaptureLoop)
{
IsBackground = true,
Priority = ThreadPriority.AboveNormal
};
_captureThread.Start();
_log.Info("开始连续采集");
}
private void ContinuousCaptureLoop()
{
while (_isContinuousCapture)
{
try
{
HObject frame = CaptureSingleFrame();
if (frame != null && _frameCallback != null)
{
_frameCallback(frame);
}
Thread.Sleep(10); // 控制采集帧率
}
catch (Exception ex)
{
_log.Error($"连续采集异常:{ex.Message}");
Thread.Sleep(100);
}
}
}
// 其他方法:StopContinuousCapture、SetCameraParam、ConvertToHObject、GetIpFromDeviceInfo、Dispose
// 完整代码见文末源码仓库
}
}
using System;
using System.Threading;
using HalconDotNet;
using VisionFramework.Model;
using log4net;
namespace VisionFramework.Capture.Implement
{
public class LocalImageCapture : ICapture
{
private static readonly ILog _log = LogManager.GetLogger(typeof(LocalImageCapture));
private CameraConfig _config;
private bool _isContinuousCapture;
private Thread _captureThread;
private Action<HObject> _frameCallback;
public bool Init(CameraConfig config)
{
try
{
if (!System.IO.File.Exists(config.ImagePath))
throw new Exception($"本地图片不存在:{config.ImagePath}");
_config = config;
_log.Info("本地图片采集初始化成功");
return true;
}
catch (Exception ex)
{
_log.Error($"本地图片采集初始化失败:{ex.Message}");
return false;
}
}
public HObject CaptureSingleFrame()
{
HObject image = null;
try
{
HOperatorSet.ReadImage(out image, _config.ImagePath);
return image;
}
catch (Exception ex)
{
_log.Error($"读取本地图片失败:{ex.Message}");
image?.Dispose();
return null;
}
}
// 其他方法:StartContinuousCapture、StopContinuousCapture、SetCameraParam、Dispose
}
}
处理模块是框架的“核心”,封装Halcon算法,通过
IVisionProcess接口统一调用,支持动态切换算法。
using System;
using HalconDotNet;
using VisionFramework.Model;
namespace VisionFramework.Process
{
/// <summary>
/// 视觉处理结果模型
/// </summary>
public class VisionResult
{
public bool IsSuccess { get; set; } // 处理是否成功
public string Message { get; set; } // 结果描述
public HObject ProcessedImage { get; set; } // 处理后的图像(含标注)
public double DefectArea { get; set; } // 缺陷面积(示例字段)
public double SizeValue { get; set; } // 尺寸测量值(示例字段)
// 可根据需求扩展其他字段
}
/// <summary>
/// 视觉处理接口
/// </summary>
public interface IVisionProcess : IDisposable
{
/// <summary>
/// 初始化处理算法(加载模板、设置参数)
/// </summary>
bool Init(AlgorithmConfig config);
/// <summary>
/// 执行图像处理
/// </summary>
VisionResult Execute(HObject inputImage);
/// <summary>
/// 更新算法参数
/// </summary>
bool UpdateParam(string paramName, object value);
}
}
using System;
using HalconDotNet;
using VisionFramework.Model;
using log4net;
namespace VisionFramework.Process.Implement
{
public class DefectDetectionProcess : IVisionProcess
{
private static readonly ILog _log = LogManager.GetLogger(typeof(DefectDetectionProcess));
private AlgorithmConfig _config;
private HObject _template; // 模板图像(可选)
public bool Init(AlgorithmConfig config)
{
try
{
_config = config;
// 加载模板(如果需要模板匹配定位)
if (!string.IsNullOrEmpty(config.TemplatePath) && System.IO.File.Exists(config.TemplatePath))
{
HOperatorSet.ReadImage(out _template, config.TemplatePath);
_log.Info("模板加载成功");
}
_log.Info("缺陷检测算法初始化成功");
return true;
}
catch (Exception ex)
{
_log.Error($"缺陷检测算法初始化失败:{ex.Message}");
Dispose();
return false;
}
}
public VisionResult Execute(HObject inputImage)
{
VisionResult result = new VisionResult { IsSuccess = false };
HObject processedImage = null;
HObject defectRegion = null;
try
{
if (inputImage == null)
throw new Exception("输入图像为空");
// 复制输入图像,避免修改原图像
HOperatorSet.CopyImage(inputImage, out processedImage);
// Halcon缺陷检测核心流程:预处理→二值化→形态学操作→缺陷提取→筛选
HObject grayImage, binaryImage, morphImage;
// 1. 转灰度图(如果输入是彩色图)
HOperatorSet.CountChannels(inputImage, out HTuple channels);
if (channels.I == 3)
HOperatorSet.Rgb1ToGray(inputImage, out grayImage);
else
HOperatorSet.CopyImage(inputImage, out grayImage);
// 2. 图像预处理(均值滤波,降噪)
HOperatorSet.MeanImage(grayImage, out HObject smoothImage, 5, 5);
// 3. 二值化(根据配置的阈值)
HOperatorSet.Threshold(smoothImage, out binaryImage, _config.Threshold, 255);
// 4. 形态学操作(开运算,去除小噪声)
HOperatorSet.GenCircle(out HObject structElement, 3, 3, 3);
HOperatorSet.OpeningCircle(binaryImage, out morphImage, 3);
// 5. 提取缺陷区域(假设缺陷是暗区域,取反)
HOperatorSet.Invert(morphImage, out defectRegion);
// 6. 筛选缺陷(面积过滤)
HOperatorSet.SelectShape(defectRegion, out defectRegion, "area", "and", _config.MinDefectArea, 100000);
// 7. 计算缺陷面积
HOperatorSet.AreaCenter(defectRegion, out HTuple defectArea, out _, out _);
result.DefectArea = defectArea.D;
// 8. 在处理后的图像上标注缺陷(红色)
HOperatorSet.SetColor(processedImage, "red");
HOperatorSet.SetDraw(processedImage, "fill");
HOperatorSet.DispRegion(defectRegion, processedImage, 1);
// 9. 设置结果
result.IsSuccess = true;
result.Message = defectArea.I == 0 ? "未检测到缺陷" : $"检测到缺陷,面积:{defectArea.D:F2}像素";
result.ProcessedImage = processedImage;
result.DefectArea = defectArea.D;
_log.Info(result.Message);
}
catch (Exception ex)
{
result.Message = $"处理失败:{ex.Message}";
_log.Error(result.Message);
processedImage?.Dispose();
processedImage = null;
}
finally
{
// 释放中间变量,避免内存泄漏
defectRegion?.Dispose();
grayImage?.Dispose();
binaryImage?.Dispose();
morphImage?.Dispose();
// ... 其他中间变量释放
}
result.ProcessedImage = processedImage;
return result;
}
// 其他方法:UpdateParam、Dispose
}
}
可视化模块负责显示原始图像、处理后的图像、缺陷标注,直接使用Halcon的
HWindowControl控件(需在WinForms中添加)。
using System;
using HalconDotNet;
using log4net;
namespace VisionFramework.Visualization
{
public class VisionVisualizer
{
private static readonly ILog _log = LogManager.GetLogger(typeof(VisionVisualizer));
private HWindowControl _hWindowControl;
private HWindow _halconWindow;
/// <summary>
/// 初始化可视化控件
/// </summary>
public void Init(HWindowControl hWindowControl)
{
try
{
_hWindowControl = hWindowControl;
_halconWindow = _hWindowControl.HalconID;
// 设置窗口参数
_halconWindow.SetDraw("fill");
_halconWindow.SetColor("white");
_log.Info("可视化控件初始化成功");
}
catch (Exception ex)
{
_log.Error($"可视化控件初始化失败:{ex.Message}");
throw;
}
}
/// <summary>
/// 显示图像
/// </summary>
public void ShowImage(HObject image)
{
try
{
if (image == null || _halconWindow == null)
return;
// 清空窗口
_halconWindow.ClearWindow();
// 获取图像尺寸,自适应窗口显示
HOperatorSet.GetImageSize(image, out HTuple width, out HTuple height);
_halconWindow.SetPart(0, 0, height - 1, width - 1);
_halconWindow.DispObj(image);
}
catch (Exception ex)
{
_log.Error($"图像显示失败:{ex.Message}");
}
}
/// <summary>
/// 标注文本(如结果信息)
/// </summary>
public void DrawText(string text, int x, int y)
{
try
{
if (_halconWindow == null)
return;
_halconWindow.SetColor("yellow");
_halconWindow.SetTposition(y, x);
_halconWindow.WriteString(text);
}
catch (Exception ex)
{
_log.Error($"文本标注失败:{ex.Message}");
}
}
// 其他方法:DrawROI、DrawDefectMark等
}
}
using System;
using System.Windows.Forms;
using VisionFramework.Config;
using VisionFramework.Capture;
using VisionFramework.Capture.Implement;
using VisionFramework.Process;
using VisionFramework.Process.Implement;
using VisionFramework.Visualization;
using VisionFramework.Model;
using HalconDotNet;
namespace VisionFramework.Demo
{
public partial class MainForm : Form
{
private GlobalConfig _globalConfig;
private ICapture _capture;
private IVisionProcess _visionProcess;
private VisionVisualizer _visualizer;
private bool _isRunning = false;
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
try
{
// 1. 加载配置
_globalConfig = ConfigManager.LoadConfig();
UpdateConfigUI();
// 2. 初始化可视化
_visualizer = new VisionVisualizer();
_visualizer.Init(hWindowControl1);
// 3. 初始化采集模块(根据配置的相机类型)
_capture = CreateCaptureInstance(_globalConfig.CameraConfig.CameraType);
bool captureInit = _capture.Init(_globalConfig.CameraConfig);
lblCaptureStatus.Text = captureInit ? "采集模块就绪" : "采集模块初始化失败";
lblCaptureStatus.ForeColor = captureInit ? Color.Green : Color.Red;
// 4. 初始化处理模块(根据配置的算法类型)
_visionProcess = CreateProcessInstance(_globalConfig.AlgorithmConfig.ProcessType);
bool processInit = _visionProcess.Init(_globalConfig.AlgorithmConfig);
lblProcessStatus.Text = processInit ? "算法模块就绪" : "算法模块初始化失败";
lblProcessStatus.ForeColor = processInit ? Color.Green : Color.Red;
}
catch (Exception ex)
{
MessageBox.Show($"程序初始化失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
Close();
}
}
/// <summary>
/// 根据相机类型创建采集实例(工厂模式)
/// </summary>
private ICapture CreateCaptureInstance(string cameraType)
{
return cameraType switch
{
"HikVision" => new HikVisionCapture(),
"Basler" => new BaslerCapture(), // 需实现Basler相机采集类
"LocalImage" => new LocalImageCapture(),
_ => throw new ArgumentException($"不支持的相机类型:{cameraType}")
};
}
/// <summary>
/// 根据算法类型创建处理实例(工厂模式)
/// </summary>
private IVisionProcess CreateProcessInstance(string processType)
{
return processType switch
{
"DefectDetection" => new DefectDetectionProcess(),
"SizeMeasurement" => new SizeMeasurementProcess(), // 需实现尺寸测量类
"BarcodeRead" => new BarcodeReadProcess(), // 需实现条码识别类
_ => throw new ArgumentException($"不支持的算法类型:{processType}")
};
}
/// <summary>
/// 单帧采集处理按钮
/// </summary>
private void btnSingleCapture_Click(object sender, EventArgs e)
{
if (_capture == null || _visionProcess == null)
{
MessageBox.Show("采集模块或算法模块未初始化", "警告");
return;
}
// 1. 采集单帧图像
HObject inputImage = _capture.CaptureSingleFrame();
if (inputImage == null)
{
MessageBox.Show("采集图像失败", "警告");
return;
}
// 2. 执行图像处理
VisionResult result = _visionProcess.Execute(inputImage);
// 3. 显示结果
_visualizer.ShowImage(result.ProcessedImage ?? inputImage);
_visualizer.DrawText(result.Message, 10, 20);
lblResult.Text = result.Message;
// 4. 释放图像资源
inputImage.Dispose();
result.ProcessedImage?.Dispose();
}
/// <summary>
/// 连续采集处理按钮
/// </summary>
private void btnContinuousCapture_Click(object sender, EventArgs e)
{
if (_isRunning)
{
// 停止连续采集
_capture.StopContinuousCapture();
btnContinuousCapture.Text = "开始连续采集";
lblCaptureStatus.Text = "采集已停止";
_isRunning = false;
}
else
{
// 开始连续采集
_capture.StartContinuousCapture((inputImage) =>
{
// 采集回调中执行处理(跨线程更新UI)
Invoke(new Action(() =>
{
VisionResult result = _visionProcess.Execute(inputImage);
_visualizer.ShowImage(result.ProcessedImage ?? inputImage);
_visualizer.DrawText(result.Message, 10, 20);
lblResult.Text = result.Message;
// 释放资源
inputImage.Dispose();
result.ProcessedImage?.Dispose();
}));
});
btnContinuousCapture.Text = "停止连续采集";
lblCaptureStatus.Text = "正在连续采集...";
_isRunning = true;
}
}
// 其他方法:保存配置、更新参数、FormClosing时释放资源
}
}
halcondotnet.dll未复制到输出目录。解决方案:右键引用的
halcondotnet.dll→属性→复制本地→设为
True。原因2:Halcon是32位版本,项目目标平台是x64。解决方案:卸载32位Halcon,安装64位版本,确保项目目标平台为x64。
MeanImage替代
GaussFilter)、减少形态学操作的核大小。原因3:相机曝光时间过长。解决方案:在满足成像质量的前提下,减小曝光时间,提高采集帧率。
HObject未手动释放。解决方案:所有
HObject使用后必须调用
Dispose(),尤其是中间变量(如滤波后的图像、缺陷区域)。原因2:连续采集时未释放每帧图像资源。解决方案:在采集回调中,处理完每帧图像后立即释放
inputImage和
processedImage。原因3:协程/线程未正确终止,导致资源无法回收。解决方案:程序关闭或停止采集时,终止所有线程,释放
ICapture和
IVisionProcess实例。
AutoThreshold算子),或根据图像灰度直方图动态调整阈值。原因3:相机参数波动(如曝光时间、增益被意外修改)。解决方案:初始化相机后锁定关键参数,避免误操作;定期校准相机。
HObject已被释放,仍尝试使用。解决方案:检查代码中
Dispose()的调用顺序,确保算子执行时
HObject未被释放。原因2:图像格式不匹配(如算子要求灰度图,输入是彩色图)。解决方案:在算法开头统一将图像转为灰度图,避免格式错误。
HWindowControl)。解决方案:使用
Invoke或
BeginInvoke跨线程更新UI,如本文代码中
Invoke(new Action(() => { ... }))。
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)),或以管理员身份运行程序。
Threshold替代
BinaryThreshold,
MeanImage替代
GaussFilter)。并行处理:多相机场景下,使用多线程并行处理不同相机的图像(注意线程安全,避免共享
HObject)。
HObject对象池:连续采集场景下,复用
HObject实例,避免频繁创建和销毁(如创建一个
HObject池,每次从池中获取实例,使用后归还)。减少中间变量:合并多个Halcon算子,减少中间
HObject的创建(如将“转灰度+滤波”合并为一个步骤,减少一次图像复制)。
async/await异步模式,进一步提升UI响应速度。算法参数动态调整:支持在程序运行时调整算法参数(如阈值、缺陷面积阈值),实时生效,无需重启程序。
C#+Halcon打造的视觉通用框架,核心不是“封装多少算子”,而是“让开发者从重复劳动中解放出来”——不用再写采集初始化、配置读写、可视化这些通用代码,专注于具体的算法逻辑和业务场景。
本文的框架已在多个实际项目中验证,支持海康、Basler相机和多种检测算法,可直接复用(需根据硬件参数调整配置文件和相机实现类)。框架的设计思路比代码本身更重要——面向接口、解耦、可扩展,这些原则能让你的框架适应不同项目的需求,避免后期大面积重构。