当前位置: 首页 > news >正文

双相机结合halcon的条码检测

以下是针对提供的C#代码的详细注释和解释,结合Halcon库的功能和代码结构进行说明:

---

### **代码整体结构**
该代码是一个基于Halcon库的条码扫描类`GeneralBarcodeScan`,支持单台或双台相机的条码检测,并通过回调接口返回结果。主要功能包括:
1. 相机初始化与图像采集。
2. 条码检测模型配置(静区、容错等)。
3. 单/双相机图像处理与条码识别。
4. 资源管理(释放相机、图像对象等)。

---

### **关键代码注释**

#### **1. 类定义与成员变量**
```csharp
public class GeneralBarcodeScan : IDisposable
{
    private string mLastErrorMsg;  // 存储最后的错误信息
    private ICcdCallback mCallback;  // 相机回调接口,用于处理扫描到的条码数据
    private HTuple mHvAcqHandle;    // 第一台相机的句柄
    private HTuple mHvAcqHandle2;   // 第二台相机的句柄
    private HObject mHoImage;       // 存储第一台相机捕获的图像
    private HObject mHoImage2;      // 存储第二台相机捕获的图像
    // ... 其他窗口相关变量 ...
}
```
- **功能**:定义类的成员变量,包括相机句柄、图像对象、错误信息和回调接口。
- **关键点**:`HTuple`是Halcon中常用的参数传递类型,`HObject`用于存储图像数据。

---

#### **2. 构造函数与资源初始化**
```csharp
public GeneralBarcodeScan()
{
    mHvAcqHandle = new HTuple();    // 初始化相机句柄
    mHvAcqHandle2 = new HTuple();
    HOperatorSet.GenEmptyObj(out mHoImage);  // 创建空图像对象
    HOperatorSet.GenEmptyObj(out mHoImage2);
    // 设置系统默认图像大小为512x512
    HOperatorSet.SetSystem("width", 512);
    HOperatorSet.SetSystem("height", 512);
    // 初始化第二个窗口的尺寸为0
    mWndWidth2 = 0;
    mWndHeight2 = 0;
}
```
- **功能**:初始化类成员变量,并设置Halcon的默认图像尺寸。
- **关键函数**:
  - `GenEmptyObj`:创建空的`HObject`对象。
  - `SetSystem`:设置Halcon系统参数(如默认图像尺寸)。

---

#### **3. 相机打开与初始化方法**
```csharp
public bool OpenImageAcq(HWindow hHalconWnd, int wndWidth, int wndHeight, int deviceQty)
{
    this.mHPreviewWnd = hHalconWnd;  // 设置显示窗口
    this.mWndWidth = wndWidth;
    this.mWndHeight = wndHeight;
    // ... 省略部分代码 ...
    // 获取相机设备信息
    HOperatorSet.InfoFramegrabber("GigEVision2", "device", out hvDeviceInfo, out hvDeviceInfoValues);
    if (hvDeviceInfoValues.Length == 0)  // 检查设备是否存在
    {
        this.mLastErrorMsg = "获取不到相机设备";
        return false;
    }
    // 打开第一台相机
    HOperatorSet.OpenFramegrabber("GigEVision2", 0, 0, 0, 0, 0, 0, "progressive",
        -1, "default", -1, "false", "default", hvDeviceInfoValues.TupleSelect(0), 0, -1, out mHvAcqHandle);
    // 设置相机超时参数
    HOperatorSet.SetFramegrabberParam(mHvAcqHandle, "grab_timeout", 2000);
    HOperatorSet.GrabImageStart(mHvAcqHandle, -1);  // 开始异步图像采集
    // ... 双相机处理逻辑 ...
    return true;
}
```
- **功能**:打开相机并初始化参数,支持单/双相机配置。
- **关键函数**:
  - `OpenFramegrabber`:通过GigE Vision协议连接相机。
  - `GrabImageStart`:开始异步图像采集(`-1`表示连续采集)。
- **参数说明**:
  - `"GigEVision2"`:指定相机协议类型。
  - `hvDeviceInfoValues.TupleSelect(0)`:选择第一个检测到的相机设备。

---

#### **4. 相机关闭与资源释放**
```csharp
public void CloseImageAcq()
{
    if (this.mHoImage != null)
    {
        this.mHoImage.Dispose();  // 释放图像对象
        this.mHoImage = null;
    }
    if (this.mHvAcqHandle != null)
    {
        HOperatorSet.CloseFramegrabber(this.mHvAcqHandle);  // 关闭相机
        this.mHvAcqHandle.Dispose();
        this.mHvAcqHandle = null;
    }
    // ... 处理第二个相机和窗口 ...
}
```
- **功能**:释放相机句柄、图像对象和窗口资源。
- **关键函数**:
  - `CloseFramegrabber`:关闭相机连接。
  - `Dispose`:释放Halcon对象资源。

---

#### **5. 条码扫描核心逻辑(单相机)**
```csharp
public void StartScanBarcode(int qty)
{
    List<string> barcodeList = new List<string>();
    HObject hoImage;  // 当前捕获的图像
    HTuple hvBarCodeHandle;  // 条码检测模型句柄
    // ... 省略部分代码 ...
    // 创建条码检测模型并配置参数
    HOperatorSet.CreateBarCodeModel(new HTuple("quiet_zone"), new HTuple("true"), out hvBarCodeHandle);
    HOperatorSet.SetBarCodeParam(hvBarCodeHandle, "majority_voting", "true");  // 启用多数表决
    HOperatorSet.SetBarCodeParam(hvBarCodeHandle, "element_size_variable", "true");  // 允许模块尺寸变化
    // 捕获图像并检测条码
    HOperatorSet.GrabImageAsync(out hoImage, mHvAcqHandle, -1);  // 异步获取图像
    HOperatorSet.FindBarCode(hoImage, out hoSymbolRegions, hvBarCodeHandle, "Code 128", out hvDecodedDataStrings);
    // 处理检测结果
    for (int i = 0; i < hvDecodedDataStrings.Length; i++)
    {
        barcodeList.Add(hvDecodedDataStrings[i]);  // 存储解码后的条码数据
    }
    // 释放资源并触发回调
    if (mCallback != null) mCallback.FoundBarcode(barcodeList);  // 通知回调函数
}
```
- **功能**:捕获图像并检测Code 128条码,通过回调返回结果。
- **关键步骤**:
  1. **创建检测模型**:`CreateBarCodeModel`配置静区检测。
  2. **设置参数**:启用多数表决(减少误检)、允许模块尺寸变化(适应变形条码)。
  3. **图像采集**:`GrabImageAsync`异步获取单张图像。
  4. **条码检测**:`FindBarCode`返回解码结果。
- **参数解释**:
  - `quiet_zone`:检测条码周围的空白区域,提升稳定性。
  - `majority_voting`:通过多条扫描线投票选择最终解码结果。

---

#### **6. 双相机拼接与检测**
```csharp
public void StartScanBarcodeBy2Device(bool reverse)
{
    HObject hoImage, hoImage2, hoImages;  // 两台相机的图像
    // ... 省略部分代码 ...
    // 捕获并拼接图像
    HOperatorSet.GrabImageAsync(out hoImage, mHvAcqHandle, -1);
    HOperatorSet.GrabImageAsync(out hoImage2, mHvAcqHandle2, -1);
    if (reverse)  // 根据参数决定拼接顺序
    {
        HOperatorSet.ConcatObj(hoImages, hoImage2, out hoImages);
        HOperatorSet.ConcatObj(hoImages, hoImage, out hoImages);
    }
    else
    {
        HOperatorSet.ConcatObj(hoImages, hoImage, out hoImages);
        HOperatorSet.ConcatObj(hoImages, hoImage2, out hoImages);
    }
    HOperatorSet.TileImages(hoImages, out hoObjectsConcat, 2, "horizontal");  // 水平拼接
    // 执行检测(后续步骤与单相机相同)
}
```
- **功能**:同时捕获双相机图像并拼接,再进行条码检测。
- **关键函数**:
  - `ConcatObj`:合并图像对象。
  - `TileImages`:将图像拼接成单个图像(`horizontal`表示水平方向)。
- **参数`reverse`**:控制图像拼接的顺序(是否反转双相机的顺序)。

---

#### **7. 窗口适配与显示**
```csharp
private HTuple[] GetDisplayRect(int wndWidth, int wndHeight, int imageWidth, int imageHeight)
{
    // 计算图像在窗口中的显示区域,保持比例
    double widthRatio = (double)imageWidth / wndWidth;
    double heightRatio = (double)imageHeight / wndHeight;
    HTuple row1, column1, row2, column2;
    if (widthRatio >= heightRatio)  // 宽度占优,以宽度比例缩放
    {
        row1 = -(wndHeight * widthRatio - imageHeight) / 2;
        column1 = 0;
        row2 = row1 + wndHeight * widthRatio;
        column2 = column1 + wndWidth * widthRatio;
    }
    else  // 高度占优,以高度比例缩放
    {
        row1 = 0;
        column1 = -(wndWidth * heightRatio - imageWidth) / 2;
        row2 = row1 + wndHeight * heightRatio;
        column2 = column1 + wndWidth * heightRatio;
    }
    return new HTuple[] { row1, column1, row2, column2 };
}
```
- **功能**:根据窗口尺寸计算图像显示区域,保持图像比例。
- **实现逻辑**:
  - 计算宽高比,选择占优方向进行缩放。
  - 调整显示区域的坐标,确保图像居中显示。

---

### **关键函数与参数说明**
#### **Halcon函数**
| 函数名 | 功能 | 参数示例 |
|--------|------|----------|
| `OpenFramegrabber` | 打开相机 | `"GigEVision2"`, 设备标识符 |
| `GrabImageAsync` | 异步抓取图像 | `out HObject`, 相机句柄 |
| `CreateBarCodeModel` | 创建条码检测模型 | `'quiet_zone'`, `'true'` |
| `FindBarCode` | 检测图像中的条码 | `'Code 128'` |
| `TileImages` | 拼接图像 | `'horizontal'` |

#### **参数解释**
| 参数名 | 作用 |
|--------|------|
| `quiet_zone` | 启用条码周围空白区域检测 |
| `majority_voting` | 启用多条扫描线投票机制 |
| `element_size_variable` | 允许条码模块尺寸变化(适应变形条码) |
| `start_stop_tolerance` | 设置起始符/终止符的容错级别 |

---

### **代码优势与注意事项**
#### **优势**
1. **资源管理**:通过`IDisposable`接口确保相机和图像对象的正确释放。
2. **双相机支持**:可扩展至多相机协同检测,提升检测范围。
3. **条码检测优化**:通过静区和容错参数提升鲁棒性。

#### **注意事项**
1. **相机配置**:
   - 需确保相机型号与`GigEVision2`协议兼容。
   - 网络配置(如IP地址)需提前设置。
2. **资源泄漏风险**:
   - 必须在`using`块或`Dispose()`中调用`CloseImageAcq()`。
3. **性能优化**:
   - 双相机拼接可能增加处理时间,需根据场景调整。

---

### **典型使用流程**
```csharp
// 1. 初始化类并设置回调
var scanner = new GeneralBarcodeScan();
scanner.setCallback(new MyCcdCallback());

// 2. 打开相机(假设单相机)
scanner.OpenImageAcq(window, 1280, 720, 1);

// 3. 开始检测
scanner.StartScanBarcode(1);

// 4. 关闭资源
scanner.Dispose();
```

通过以上注释和解释,开发者可以清晰理解代码功能、参数含义及实现逻辑。

using HalconDotNet;  // 引用 Halcon 图像处理库
using Hggit.Hwodc.Common;  // 引用其他库
using System;  // 引用系统库
using System.Collections.Generic;  // 引用集合类库
using System.Linq;  // 引用 LINQ 查询库
using System.Text;  // 引用字符串处理类库
using System.Threading.Tasks;  // 引用异步任务处理库namespace Hggit.Hwodc.Halcon
{public class GeneralBarcodeScan : IDisposable{private string mLastErrorMsg;  // 保存最后的错误信息private ICcdCallback mCallback;  // 相机回调接口,用于处理扫描到的条形码/// <summary>/// 相机句柄,用于相机图像捕获/// </summary>private HTuple mHvAcqHandle;/// <summary>/// 第二个相机句柄/// </summary>private HTuple mHvAcqHandle2;/// <summary>/// 捕获的图像对象/// </summary>private HObject mHoImage;/// <summary>/// 第二个捕获的图像对象/// </summary>private HObject mHoImage2;/// <summary>/// 窗口的宽度/// </summary>private int mWndWidth;/// <summary>/// 窗口的高度/// </summary>private int mWndHeight;/// <summary>/// Halcon 图像显示窗口/// </summary>private HWindow mHPreviewWnd;/// <summary>/// Halcon 图像显示窗口内部句柄/// </summary>HTuple mHvWindowHandle;/// <summary>/// 第二个窗口宽度/// </summary>private int mWndWidth2;/// <summary>/// 第二个窗口高度/// </summary>private int mWndHeight2;/// <summary>/// 第二个窗口的句柄/// </summary>HTuple mHvWindowHandle2;public void Dispose(){// 清理资源,关闭相机及图像对象CloseImageAcq();}public GeneralBarcodeScan(){// 初始化相机句柄和图像对象mHvAcqHandle = new HTuple();mHvAcqHandle2 = new HTuple();HOperatorSet.GenEmptyObj(out mHoImage);HOperatorSet.GenEmptyObj(out mHoImage2);// 设置系统默认图像大小HOperatorSet.SetSystem("width", 512);HOperatorSet.SetSystem("height", 512);mWndWidth2 = 0;mWndHeight2 = 0;}public void setCallback(ICcdCallback callback){// 设置回调接口this.mCallback = callback;}/// <summary>/// 打开相机并初始化图像采集/// </summary>/// <returns>返回是否成功打开相机</returns>public bool OpenImageAcq(HWindow hHalconWnd, int wndWidth, int wndHeight, int deviceQty){this.mHPreviewWnd = hHalconWnd;this.mWndWidth = wndWidth;this.mWndHeight = wndHeight;// 检查操作系统是否为 Windows,设置线程安全if (HalconAPI.isWindows)HOperatorSet.SetSystem("use_window_thread", "true");HTuple hvDeviceInfo;HTuple hvDeviceInfoValues;// 获取相机设备信息HOperatorSet.InfoFramegrabber("GigEVision2", "device", out hvDeviceInfo, out hvDeviceInfoValues);// 如果没有设备信息,返回错误if (hvDeviceInfoValues.Length == 0){this.mLastErrorMsg = "获取不到相机设备";return false;}// 检查是否有两个相机设备if (deviceQty == 2){if (hvDeviceInfoValues.Length < 2){this.mLastErrorMsg = "只检测到了一个相机设备!";return false;}}// 打开第一个相机HOperatorSet.OpenFramegrabber("GigEVision2", 0, 0, 0, 0, 0, 0, "progressive",-1, "default", -1, "false", "default", hvDeviceInfoValues.TupleSelect(0), 0, -1, out mHvAcqHandle);// 设置相机参数HOperatorSet.SetFramegrabberParam(mHvAcqHandle, "grab_timeout", 2000);HOperatorSet.GrabImageStart(mHvAcqHandle, -1);// 打开第二个相机(如果有)if (deviceQty == 2){HOperatorSet.OpenFramegrabber("GigEVision2", 0, 0, 0, 0, 0, 0, "progressive",-1, "default", -1, "false", "default", hvDeviceInfoValues.TupleSelect(1), 0, -1, out mHvAcqHandle2);HOperatorSet.SetFramegrabberParam(mHvAcqHandle2, "grab_timeout", 2000);HOperatorSet.GrabImageStart(mHvAcqHandle2, -1);}// 关闭之前打开的窗口if (HDevWindowStack.IsOpen()){var hvWnd = HDevWindowStack.Pop();while (hvWnd != null){HOperatorSet.CloseWindow(hvWnd);}}// 打开显示窗口并设置窗口句柄HOperatorSet.OpenWindow(0, 0, wndWidth, wndHeight, hHalconWnd, "visible", "", out this.mHvWindowHandle);HDevWindowStack.Push(mHvWindowHandle);return true;}public void CloseImageAcq(){// 清理相机和图像资源if (HalconAPI.isWindows)HOperatorSet.SetSystem("use_window_thread", "true");if (this.mHoImage != null){this.mHoImage.Dispose();this.mHoImage = null;}if (this.mHvWindowHandle != null){this.mHvWindowHandle.Dispose();this.mHvWindowHandle = null;}if (this.mHvAcqHandle != null){HOperatorSet.CloseFramegrabber(this.mHvAcqHandle);this.mHvAcqHandle.Dispose();this.mHvAcqHandle = null;}if (mHoImage2 != null){this.mHoImage2.Dispose();this.mHoImage2 = null;}if (this.mHvWindowHandle2 != null){this.mHvWindowHandle2.Dispose();this.mHvWindowHandle2 = null;}if (this.mHvAcqHandle2 != null){HOperatorSet.CloseFramegrabber(this.mHvAcqHandle2);this.mHvAcqHandle2.Dispose();this.mHvAcqHandle2 = null;}}public string GetLastErrorMsg(){// 获取最后的错误信息return this.mLastErrorMsg;}public void GrabImage(){// 异步捕获图像并显示在窗口HObject hoImage;HTuple imageWidth;HTuple imageHeight;if (HalconAPI.isWindows)HOperatorSet.SetSystem("use_window_thread", "true");HOperatorSet.GrabImageAsync(out hoImage, mHvAcqHandle, -1);HOperatorSet.GetImageSize(hoImage, out imageWidth, out imageHeight);if (HDevWindowStack.IsOpen()){HTuple[] displayRect = GetDisplayRect(mWndWidth, mWndHeight, imageWidth, imageHeight);HOperatorSet.SetPart(HDevWindowStack.GetActive(), displayRect[0], displayRect[1], displayRect[2], displayRect[3]);}if (HDevWindowStack.IsOpen()){HOperatorSet.DispImage(hoImage, HDevWindowStack.GetActive());}if (hoImage != null){hoImage.Dispose();}if (imageWidth != null){imageWidth.Dispose();}if (imageHeight != null){imageHeight.Dispose();}}public void StartScanBarcode(int qty){// 执行条形码扫描List<string> barcodeList = new List<string>();HObject hoImage;HObject hoObjectsConcat;HObject hoSymbolRegions;HTuple hvBarCodeHandle;HTuple imageWidth;HTuple imageHeight;HTuple hvWindowHandle;int wndWidth, wndHeight;if (qty == 1){// 如果是单台相机,使用第一个相机窗口和尺寸wndWidth = this.mWndWidth;wndHeight = this.mWndHeight;hvWindowHandle = this.mHvWindowHandle;}else{// 如果是双台相机,使用第二个相机窗口和尺寸wndWidth = this.mWndWidth2;wndHeight = this.mWndHeight2;hvWindowHandle = this.mHvWindowHandle2;}HTuple hvDecodedDataStrings;// 创建空对象,用于存放图像和检测到的条形码区域HOperatorSet.GenEmptyObj(out hoObjectsConcat);HOperatorSet.GenEmptyObj(out hoSymbolRegions);// 创建条形码检测模型,指定条形码检测的类型HOperatorSet.CreateBarCodeModel("quiet_zone", "true", out hvBarCodeHandle);// 设置条形码检测的参数HOperatorSet.SetBarCodeParam(hvBarCodeHandle, "majority_voting", "true");  // 启用多数投票HOperatorSet.SetBarCodeParam(hvBarCodeHandle, "element_size_variable", "true");  // 启用可变元素大小HOperatorSet.SetBarCodeParam(hvBarCodeHandle, "start_stop_tolerance", "low");  // 设置开始/停止容差为低// 根据相机数量,选择相应的相机进行图像捕获if (qty == 1){// 如果只有一台相机,捕获图像HOperatorSet.GrabImageAsync(out hoImage, mHvAcqHandle, -1);}else{// 如果有两台相机,捕获两台相机的图像HOperatorSet.GrabImageAsync(out hoImage, mHvAcqHandle, -1);}// 获取图像的尺寸HOperatorSet.GetImageSize(hoImage, out imageWidth, out imageHeight);// 根据窗口尺寸和图像尺寸,计算显示区域if (HDevWindowStack.IsOpen()){HTuple[] displayRect = GetDisplayRect(wndWidth, wndHeight, imageWidth, imageHeight);HOperatorSet.SetPart(hvWindowHandle, displayRect[0], displayRect[1], displayRect[2], displayRect[3]);}// 显示捕获的图像if (HDevWindowStack.IsOpen()){HOperatorSet.DispImage(hoImage, hvWindowHandle);}// 执行条形码查找HOperatorSet.FindBarCode(hoImage, out hoSymbolRegions, hvBarCodeHandle,"Code 128", out hvDecodedDataStrings);// 显示条形码区域if (HDevWindowStack.IsOpen()){HOperatorSet.DispRegion(hoSymbolRegions, hvWindowHandle);}// 将找到的条形码数据加入列表for (int i = 0; i < hvDecodedDataStrings.Length; i++){string item = hvDecodedDataStrings[i];barcodeList.Add(string.Copy(item));}// 释放资源if (hoObjectsConcat != null){hoObjectsConcat.Dispose();}if (hoSymbolRegions != null){hoSymbolRegions.Dispose();}if (hvBarCodeHandle != null){hvBarCodeHandle.Dispose();}if (hoImage != null){hoImage.Dispose();}if (imageWidth != null){imageWidth.Dispose();}if (imageHeight != null){imageHeight.Dispose();}if (hvDecodedDataStrings != null){hvDecodedDataStrings.Dispose();}// 调用回调函数,将条形码数据返回if (mCallback != null){mCallback.FoundBarcode(barcodeList);}}public void StartScanBarcodeBy2Device(bool revease){// 使用两台相机进行条形码扫描List<string> barcodeList = new List<string>();HObject hoImage;HObject hoImage2;HObject hoImages;HObject hoObjectsConcat;HObject hoSymbolRegions;HTuple hvBarCodeHandle;HTuple imageWidth;HTuple imageHeight;HTuple hvWindowHandle;int wndWidth, wndHeight;// 设置第一个相机的窗口尺寸wndWidth = this.mWndWidth;wndHeight = this.mWndHeight;hvWindowHandle = this.mHvWindowHandle;HTuple hvDecodedDataStrings;// 创建空对象,用于存放图像和检测到的条形码区域HOperatorSet.GenEmptyObj(out hoImage);HOperatorSet.GenEmptyObj(out hoImage2);HOperatorSet.GenEmptyObj(out hoImages);HOperatorSet.GenEmptyObj(out hoObjectsConcat);HOperatorSet.GenEmptyObj(out hoSymbolRegions);// 创建条形码检测模型HOperatorSet.CreateBarCodeModel("quiet_zone", "true", out hvBarCodeHandle);// 设置条形码检测的参数HOperatorSet.SetBarCodeParam(hvBarCodeHandle, "majority_voting", "true");HOperatorSet.SetBarCodeParam(hvBarCodeHandle, "element_size_variable", "true");HOperatorSet.SetBarCodeParam(hvBarCodeHandle, "start_stop_tolerance", "low");// 捕获第一台相机的图像HOperatorSet.GrabImageAsync(out hoImage, mHvAcqHandle, -1);// 捕获第二台相机的图像HOperatorSet.GrabImageAsync(out hoImage2, mHvAcqHandle2, -1);// 根据 `revease` 参数的值,决定图像的拼接顺序if (revease){HOperatorSet.ConcatObj(hoImages, hoImage2, out hoImages);HOperatorSet.ConcatObj(hoImages, hoImage, out hoImages);}else{HOperatorSet.ConcatObj(hoImages, hoImage, out hoImages);HOperatorSet.ConcatObj(hoImages, hoImage2, out hoImages);}// 将两台相机的图像拼接在一起HOperatorSet.TileImages(hoImages, out hoObjectsConcat, 2, "horizontal");// 获取拼接后的图像尺寸HOperatorSet.GetImageSize(hoObjectsConcat, out imageWidth, out imageHeight);// 根据窗口尺寸和图像尺寸,计算显示区域if (HDevWindowStack.IsOpen()){HTuple[] displayRect = GetDisplayRect(wndWidth, wndHeight, imageWidth, imageHeight);HOperatorSet.SetPart(hvWindowHandle, displayRect[0], displayRect[1], displayRect[2], displayRect[3]);}// 显示拼接后的图像if (HDevWindowStack.IsOpen()){HOperatorSet.DispImage(hoObjectsConcat, hvWindowHandle);}// 执行条形码查找HOperatorSet.FindBarCode(hoObjectsConcat, out hoSymbolRegions, hvBarCodeHandle,"Code 128", out hvDecodedDataStrings);// 显示条形码区域if (HDevWindowStack.IsOpen()){HOperatorSet.DispRegion(hoSymbolRegions, hvWindowHandle);}// 将找到的条形码数据加入列表for (int i = 0; i < hvDecodedDataStrings.Length; i++){string item = hvDecodedDataStrings[i];barcodeList.Add(string.Copy(item));}// 释放资源if (hoObjectsConcat != null){hoObjectsConcat.Dispose();}if (hoImages != null){hoImages.Dispose();}if (hoSymbolRegions != null){hoSymbolRegions.Dispose();}if (hvBarCodeHandle != null){hvBarCodeHandle.Dispose();}if (hoImage != null){hoImage.Dispose();}if (hoImage2 != null){hoImage2.Dispose();}if (imageWidth != null){imageWidth.Dispose();}if (imageHeight != null){imageHeight.Dispose();}if (hvDecodedDataStrings != null){hvDecodedDataStrings.Dispose();}// 调用回调函数,将条形码数据返回if (mCallback != null){mCallback.FoundBarcode(barcodeList);}}/// <summary>/// 计算显示区域的矩形,以适应图像与窗口的大小比例/// </summary>/// <param name="wndWidth">窗口宽度</param>/// <param name="wndHeight">窗口高度</param>/// <param name="imageWidth">图像宽度</param>/// <param/// <summary>/// 计算显示区域的矩形,以适应图像与窗口的大小比例/// </summary>/// <param name="wndWidth">窗口宽度</param>/// <param name="wndHeight">窗口高度</param>/// <param name="imageWidth">图像宽度</param>/// <param name="imageHeight">图像高度</param>/// <returns>显示区域矩形</returns>private HTuple[] GetDisplayRect(int wndWidth, int wndHeight, int imageWidth, int imageHeight){// 计算图像与窗口的宽高比例double widthRatio = (1.0) * imageWidth / wndWidth;double heightRatio = (1.0) * imageHeight / wndHeight;HTuple row1, colume1, row2, colume2;// 如果图像的宽度比高度占优,则以宽度比例为主if (widthRatio >= heightRatio){row1 = -(1.0) * (wndHeight * widthRatio - imageHeight) / 2;colume1 = 0;row2 = row1 + wndHeight * widthRatio;colume2 = colume1 + wndWidth * widthRatio;}else{// 否则,以高度比例为主row1 = 0;colume1 = -(1.0) * (wndWidth * heightRatio - imageWidth) / 2;row2 = row1 + wndHeight * heightRatio;colume2 = colume1 + wndWidth * heightRatio;}// 返回显示区域的四个坐标点(行列)return new HTuple[] { row1, colume1, row2, colume2 };}}
}

相关文章:

双相机结合halcon的条码检测

以下是针对提供的C#代码的详细注释和解释&#xff0c;结合Halcon库的功能和代码结构进行说明&#xff1a; --- ### **代码整体结构** 该代码是一个基于Halcon库的条码扫描类GeneralBarcodeScan&#xff0c;支持单台或双台相机的条码检测&#xff0c;并通过回调接口返回结果。…...

Transformer Decoder Block的几个优化方案

写在前面 在大型语言模型(LLM)的演进浪潮中,Transformer 架构凭借其强大的并行计算能力和对长距离依赖的出色捕捉,奠定了核心地位。然而,标准的 Transformer Decoder Block 遵循着一种相对固定的模式:先进行自注意力(Self-Attention)捕捉上下文信息,再通过前馈神经网…...

工业科学级天文相机:跨界融合的高精密成像解决方案

随着国内科技的快速发展&#xff0c;工业相机领域正悄然兴起一场"天文级"的技术革命。这类兼具工业设备可靠性与天文观测精度的特殊相机&#xff0c;正在半导体制造、天文观测、空间探测等领域开辟新的应用疆域。其核心技术突破不仅体现在传感器性能的提升&#xff0…...

颠覆传统!复旦微软联合研发MagicMotion,重新定义图生视频可能性

导读简介&#xff1a; 尽管基于DiT的模型在生成高质量和长视频方面表现出色&#xff0c;但许多文本到视频的方法在精确控制物体运动和相机运动等属性方面存在不足。因此&#xff0c;细粒度轨迹可控的视频生成技术应运而生&#xff0c;这对于在现实场景中生成可控视频至关重要。…...

华为数字芯片机考2025合集5已校正

1. 题目内容 下列选项中&#xff08;&#xff09;不是 Verilog HDL 的关键字。&#xff08;&#xff09; A. tri B. for C. force D. edge 解析 1. Verilog 关键字分类 Verilog 关键字是语言预定义的保留字&#xff0c;用于语法结构或特定功能。 2. 选项分析 选项类型说明…...

QML Loader:延迟加载与动态切换

目录 引言相关阅读工程结构LoaderDelay.qml - 延迟加载实现完整代码HeavyComponent.qml代码解析运行效果 LoaderSwitch.qml - 动态切换组件完整代码代码解析运行效果 Main.qml - 主界面实现完整代码主界面结构代码解析 总结下载链接 引言 QML的Loader组件提供了一种强大的机制…...

C语言--常用的链表操作

利用C语言实现链表&#xff0c;并定义一些常用的操作 文章目录 链表定义新建一个链表结点打印链表插入结点头插法&#xff08;常用&#xff09;运行 尾插法&#xff08;使用较少&#xff09;运行 返回链表长度链表转置运行 合并两个有序的链表运行 删除最小结点运行 打印倒数第…...

ngx_conf_param

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_conf_param-CSDN博客 定义在 src\core\ngx_conf_file.c char * ngx_conf_param(ngx_conf_t *cf) {char *rv;ngx_str_t *param;ngx_buf_t b;ngx_conf_file_t conf_file;param &cf->cycle->conf…...

C++day9

思维导图 牛客练习 练习&#xff1a; 将我们写的 myList 迭代器里面 operator[] 和 operator 配合异常再写一遍 #include <iostream> #include <cstring> #include <cstdlib> #include <unistd.h> #include <sstream> #include <vector>…...

算法题:两数相加

题目&#xff1a;2. 两数相加 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&a…...

SCI科学论文的重要组成部分

科学论文的核心结构 科学论文通常遵循IMRAD结构&#xff0c;即&#xff1a; 引言(Introduction)方法(Methods)结果(Results)讨论(Discussion) 除此之外&#xff0c;还包括其他几个关键部分。让我为您详细介绍每个部分的作用和重要性&#xff1a; 1. 标题(Title) 标题是论文…...

Go 微服务框架 | 路由实现

文章目录 不用框架实现web接口实现简单的路由实现分组路由支持不同的请求方式支持同一个路径的不同请求方式前缀树应用前缀树完善路由代码 不用框架实现web接口 // blog main.go 文件 package mainimport ("fmt""log""net/http" )func main() {…...

2025年AI开发学习路线

目录 一、基础阶段&#xff08;2-3个月&#xff09; 1. 数学与编程基础 2. 机器学习入门 二、核心技能&#xff08;3-4个月&#xff09; 1. 深度学习与框架 2. 大模型开发&#xff08;重点&#xff09; 三、进阶方向&#xff08;3-6个月&#xff09; 1. 多模态与智能体…...

TimescaleDB 2.19.2 发布

TimescaleDB 2.19.2 已于 2025 年 4 月 7 日发布2。此次发布是基于 PostgreSQL 的开源时序数据库 TimescaleDB 的一次更新。 从 GitHub 上的 Pull Request 信息可知,此次发布主要是将相关更改合并到 2.19.x 分支,涉及到一系列的测试和构建配置,包括不同版本 PostgreSQL(如 …...

「Unity3D」TextMeshPro中的TMP_InputField,用来实现输入框的几个小问题

第一&#xff0c;正确设置Scrollbar。 设置Scrollbar之后&#xff0c;不能设置Text Component的Font Size为Auto Size&#xff0c;否则Scrollbar无法正确计算显示。 那么&#xff0c;要想自动适配字体大小&#xff0c;可以让Placeholder中的Font Size设置为Auto&#xff0c;这…...

HTML 是什么?网页创建的核心标记语言

原文&#xff1a;HTML 是什么&#xff1f;网页创建的核心标记语言 | w3cschool笔记 HTML 是什么&#xff1f; HTML 是一种标记语言&#xff0c;用于创建网页。简单来说&#xff0c;HTML 就像一本魔法书&#xff0c;它告诉电脑如何展示网页上的内容&#xff0c;比如文字、图片…...

考研单词笔记 2025.04.09

act v表现&#xff0c;行动&#xff0c;做事&#xff0c;扮演&#xff0c;充当&#xff0c;担任&#xff0c;起作用n行为&#xff0c;行动&#xff0c;法案&#xff0c;法令 action n行为&#xff0c;行动 behave v表现&#xff0c;行事&#xff0c;守规矩&#xff0c;举止端…...

map/multimap

1.概念 map中所有元素都是pair<key,value>&#xff0c;key 是map的键&#xff0c;value 是map的值 所有元素都会根据key自动排序 map/multimap属于关联式容器&#xff0c;底层结构是用二叉树实现。 map和multimap区别&#xff1a; map不允许容器中有重复key值元素 m…...

CSS 定位属性的生动比喻:以排队为例理解 relative 与 absolute

目录 一、理解标准流与队伍的类比 二、relative 定位&#xff1a;队伍中 “小范围活动” 的人 三、absolute 定位&#xff1a;队伍中 “彻底离队” 的人 在学习 CSS 的过程中&#xff0c;定位属性relative和absolute常常让初学者感到困惑。它们的行为方式和对页面布局的影响较…...

基于二叉堆实现的 PriorityQueue

基于二叉堆实现的 PriorityQueue 是一种常见的数据结构&#xff0c;广泛用于任务调度、路径搜索、事件模拟等场景。下面我将用 Java 语言实现一个简单的基于最小堆的 PriorityQueue&#xff0c;即优先级最小的元素先出队。 ✅ 实现目标 使用数组实现二叉最小堆&#xff08;即父…...

大模型分布式推理和量化部署

一、小常识 1、计算大模型占用多少显存 对于一个7B&#xff08;70亿&#xff09;参数的模型&#xff0c;每个参数使用16位浮点数&#xff08;等于 2个 Byte&#xff09;表示&#xff0c;则模型的权重大小约为&#xff1a; 7010^9 parameters2 Bytes/parameter14GB 70亿个参数…...

循环神经网络 - 长程依赖问题及改进方案

循环神经网络在学习过程中的主要问题是由于梯度消失或爆炸问题&#xff0c;很难建模长时间间隔(Long Range)的状态之间的依赖关系。 本文我们来学习长程依赖问题及其对应的改进方案&#xff0c;在这部分知识的学习过程中&#xff0c;我建议大家着重理解&#xff0c;对于数学公…...

点击抽奖功能总结

首先用户打开网页&#xff0c;映入眼帘的是一个输入框和一个提交按钮。当用户在输入框中输入自己的年龄并点击提交后&#xff0c;系统会根据输入的年龄给出相应提示。若年龄达到 60 岁&#xff0c;页面将显示一个新的抽奖区域&#xff0c;用户可以点击 “抽奖” 按钮开始抽奖。…...

AWS Bedrock生成视频详解:AI视频创作新时代已来临

💡 TL;DR: AWS Bedrock现已支持AI视频生成功能,让企业无需深厚AI专业知识即可创建高质量视频内容。本文详解Bedrock视频生成能力的工作原理、应用场景和实操指南,助你快速掌握这一革命性技术。 🎬 AWS Bedrock视频生成:改变内容创作的游戏规则 还记得几年前,制作一个专…...

理解 TOGAF®标准中的架构原则

原则是帮助组织实现其使命的基本规则和指南。它们旨在长期稳定且很少修改&#xff0c;在各个领域中充当决策和行动的指南针。在企业架构&#xff08;EA&#xff09;的背景下&#xff0c;原则在指导架构框架的开发和应用方面发挥着至关重要的作用。本文将探讨企业原则和架构原则…...

基于视觉密码的加密二值图像可逆数据隐藏

接下来&#xff0c;分享一篇论文&#xff0c;标题为《Multi-Party Reversible Data Hiding in Ciphertext Binary Images Based on Visual Cryptography》&#xff0c;由Bing Chen等人发表在《IEEE Signal Processing Letters》上。该论文提出了一种基于视觉密码学的多方可逆数…...

ubuntu22.04 中 No module named ‘_bz2‘问题解决方案

前言 本篇是介绍ubuntu22.04中 No module named ‘_bz2‘问题解决方案 网上版本很多&#xff0c;比如安装libbz库什么的&#xff0c;可能别人有用&#xff0c;但是我自己这边出了一堆问题 一、流程 1.1 查看bz2.xx.so文件 看自己的python版本&#xff0c;我新安装了个pyth…...

什么是声波,声波的传播距离受哪些因素影响?

一、声波的定义&#xff1a; 声波是一种机械波&#xff0c;它是通过介质&#xff08;如空气、水、固体等&#xff09;传播的振动。以下是关于声波的详细介绍&#xff1a; 1、声波的产生 声波是由物体的振动产生的。例如&#xff0c;人说话时&#xff0c;声带振动产生声波&…...

用PHPExcel 封装的导出方法,支持导出无限列

用PHPExcel 封装的导出方法&#xff0c;支持导出无限列 避免PHPExcel_Exception Invalid cell coordinate [1 异常错误 /*** EXCEL导出* param [string] $file_name 保存的文件名及表格工作区名&#xff0c;不加excel后缀名* param [array] $fields 二维数组* param [array] $…...

STL-stack栈和queue队列

stack栈和queue队列 在STL中 stack 和 queue 设计为容器适配器,容器适配器是使用特定容器类的封装对象作为其基础容器的类,提供一组特定的成员函数来访问其元素。 在我的STL系列中之前的容器 vector、list、deque 都是从底层类型一步步封装而来的,但是 stack 和 queue 没有…...

AI 提示词不会写?试试 PromptIDE

这段时间&#xff0c;AI 技术大爆炸 已经改变了我们的工作方式&#xff0c;而 会不会用 AI&#xff0c;已经成为区分工作能力的关键&#xff01; &#x1f4a1; 在这个AI重构工作方式的时代&#xff0c;会用和不会用AI的人正在拉开巨大差距&#xff1a; √ 高手用AI——效率飙…...

【python读取并显示遥感影像】

在Python中读取并显示遥感影像&#xff0c;可以使用rasterio库读取影像数据&#xff0c;并结合matplotlib进行可视化。以下是一个完整的示例代码&#xff1a; import rasterio import matplotlib.pyplot as plt import numpy as np# 打开遥感影像文件 with rasterio.open(path…...

代码随想录算法训练营第十三天

LeetCode题目: 110. 平衡二叉树257. 二叉树的所有路径404. 左叶子之和222. 完全二叉树的节点个数3375. 使数组的值全部为 K 的最少操作次数(每日一题) 其他: 今日总结 往期打卡 110. 平衡二叉树 跳转: 110. 平衡二叉树 学习: 代码随想录公开讲解 问题: 给定一个二叉树&#…...

TQTT_KU5P开发板教程---高速收发器之XDMA实现PCIE

文档功能介绍 本文档主要实现了通过一个叫做XDMA的IP&#xff0c;实现PCIE的测试例子。工程新建方法请参考文档《流水灯》。 Vivado创建项目 起始页&#xff08;或 file-->Project-->New 创建新工程(Create New Project) 向导起始页面 点击 Next--> Project Name(…...

蓝桥杯速成刷题清单(上)

一、1.排序 - 蓝桥云课 &#xff08;快速排序&#xff09;算法代码&#xff1a; #include <bits/stdc.h> using namespace std; const int N 5e5 10; int a[N];int main() {int n;cin >> n;for (int i 0; i < n; i) {cin >> a[i];}sort(a, a n);for …...

【FreeRTOS】二值信号量 是 消息队列 吗

在读FreeRTOS内核实现与应用开发实战指南的时候&#xff0c;书中第16章有这么一句话&#xff1a;可以将二值信号量看作只有一个消息的队列&#xff0c;incident这个队列只能为空或满&#xff08;因此称为二值&#xff09;&#xff0c;在运用时只需要之傲队列中是否由消息即可&a…...

BOTA六维力矩传感器在三层AI架构中的集成实践:从数据采集到力控闭环

随着机器人技术的迅猛发展&#xff0c;Bota六维力矩传感器成为三层AI架构中的核心组件。它通过高精度的力与力矩感知能力&#xff0c;为感知层提供实时数据支持&#xff0c;优化了决策层的判断效率&#xff0c;并确保执行层操作的精确性和安全性。 Bota贯通式力矩传感器PixOne&…...

UE5 matcap学习笔记

没难度节点&#xff0c;但是要记住这种思维&#xff0c;移动端常用&#xff1a; 原视频&#xff1a;(美学阿姨)MatCap材质原理讲解与UE5中的实现方法_哔哩哔哩_bilibili...

神经网络 - 关于简单的激活函数的思考总结

最近一直在学习神经网络&#xff0c;有一些收获&#xff0c;也有一些迷惑&#xff0c;所以驻足思考&#xff1a;为什么简单的激活函数如sigmoid函数、ReLU函数&#xff0c;当应用在神经网络的模型中&#xff0c;却可以实现对现实世界复杂的非线性关系的模拟呢&#xff1f;本文我…...

pig 权限管理开源项目学习

pig 源码 https://github.com/pig-mesh/pig 文档在其中&#xff0c;前端在文档中&#xff0c;官方视频教学也在文档中有。 第一次搭建&#xff0c;建议直接去看单体视频&#xff0c;照着做即可。 文章目录 项目结构Maven 多模块项目pig-boot 启动核心模块pig-auth 实现认证和…...

excel中的VBA指令示例(二)

。。。接上篇。 Range("D1").Select ’选择D1单元格 ActiveCell.FormulaR1C1 "装配数量" ‘单元格内容为装配数量 Range("D1").Select Selection.AutoFilter …...

基于vue3与supabase系统认证机制

1. 认证框架概述 系统采用 Supabase 作为认证和数据服务提供商&#xff0c;实现了完整的用户身份验证流程。系统使用基于 JWT (JSON Web Token) 的认证方式&#xff0c;提供了安全可靠的用户身份管理机制。 1.1 技术栈 前端: Vue 3 TypeScript状态管理: Pinia认证服务: Sup…...

【算法笔记】并查集详解

&#x1f680; 并查集&#xff08;Union-Find&#xff09;详解&#xff1a;原理、实现与优化 并查集&#xff08;Union-Find&#xff09;是一种非常高效的数据结构&#xff0c;用于处理动态连通性问题&#xff0c;即判断若干个元素是否属于同一个集合&#xff0c;并支持集合合…...

基于Redis实现短信防轰炸的Java解决方案

基于Redis实现短信防轰炸的Java解决方案 前言 在当今互联网应用中&#xff0c;短信验证码已成为身份验证的重要手段。然而&#xff0c;这也带来了"短信轰炸"的安全风险 - 恶意用户利用程序自动化发送大量短信请求&#xff0c;导致用户被骚扰和企业短信成本激增。本…...

编程中,!! 双感叹号的理解

在编程中&#xff0c;!! 双感叹号的含义取决于上下文。通常情况下&#xff0c;!! 是逻辑非操作符的双重使用&#xff0c;用来将一个值强制转换为布尔类型。 1. 逻辑非操作符 在 JavaScript 中&#xff0c;! 是逻辑非操作符&#xff0c;它会将一个值转换为布尔类型&#xff1a…...

ARM内核与寄存器

ARM内核与寄存器详解 目录 ARM架构概述ARM处理器模式 Cortex-M3内核的处理器模式Cortex-A系列处理器模式 ARM寄存器集 通用寄存器程序计数器(PC)链接寄存器(LR)堆栈指针(SP)状态寄存器(CPSR/SPSR) 协处理器寄存器NEON和VFP寄存器寄存器使用规范常见ARM指令与寄存器操作 ARM架…...

【C++进阶】关联容器:set类型

目录 一、set 基本概念 1.1 定义与特点 1.2 头文件与声明 1.3 核心特性解析 二、set 底层实现 2.1 红黑树简介 2.2 红黑树在 set 中的应用 三、set 常用操作 3.1 插入元素 3.2 删除元素 3.3 查找元素 3.4 遍历元素 3.5 性能特征 四、set 高级应用 4.1 自定义比较…...

Linux内核——X86分页机制

X86分页机制 x86的分页单元支持两种分页模式&#xff1a;常规分页与扩展分页。 常规分页采用两级结构&#xff0c;固定页大小为4KB。线性地址被划分为三个字段&#xff1a; 页目录索引&#xff08;最高10位&#xff09;页表索引&#xff08;中间10位&#xff09;页内偏移&am…...

重温Java - Java基础二

工作中常见的6中OOM 问题 堆内存OOM 堆内存OOM 是最常见的OOM了。出现堆内存OOM 问题的异常信息如下 java.lang.OutOfMemoryError: Java heap space此OOM是由于Java中的heap的最大值&#xff0c;已经不能满足需求了。 举个例子 Test public void test01(){List<OOMTest…...

回溯算法+对称剪枝——从八皇后问题到数独问题(二)

引入&#xff1a; 本节我们进一步完善八皇后问题&#xff0c;学习剪枝、八皇后残局问题 进一步领会逻辑编程的概念&#xff0c;深入体会回溯算法&#xff0c;回顾上一节提到的启发搜索策略。 回顾&#xff1a; 八皇后问题&#xff1a;我们需要在一个空棋盘上放置 n 个皇后&a…...