双相机结合halcon的条码检测

news2025/4/18 9:53:43

以下是针对提供的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 };
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2332577.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++学习之ORACLE①

目录 1.ORACLE数据库简介 2..ORACLE数据库安装 3..ORACLE体系结构 4..ORACLE基本概念 5..ORACLE基本元素 6..ORACLE数据库启动和关闭 7.SQLPLUS登录ORACLE数据库相关操作 8.SQLPLUS的基本操作 9.oracle中上课使用的方案 10.SQL语言分类 11.SQL中的select语句语法和注…

企业级开发SpringBoost玩转Elasticsearch

案例 Spring Boot 提供了 spring-data-elasticsearch 模块&#xff0c;可以方便地集成 Elasticsearch。 下面我们将详细讲解如何在 Spring Boot 中使用 Elasticsearch 8&#xff0c;并提供示例代码。 1. 添加依赖: 首先&#xff0c;需要在 pom.xml 文件中添加 spring-data-e…

从零开始的图论讲解(1)——图的概念,图的存储,图的遍历与图的拓扑排序

目录 前言 图的概念 1. 顶点和边 2. 图的分类 3. 图的基本性质 图的存储 邻接矩阵存图 邻接表存图 图的基本遍历 拓扑排序 拓扑排序是如何写的呢? 1. 统计每个节点的入度 2. 构建邻接表 3. 将所有入度为 0 的节点加入队列 4. 不断弹出队头节点&#xff0c;更新其…

SpringBoot框架—启动原理

1.SpringBootApplication注解 在讲解启动原理之前先介绍一个非常重要的注解SpringBootApplication&#xff0c;这个注解在Springboot程序的入口文件Application.java中必须添加。SpringBootApplication是一个整合了三个核心注解的组合注解。 三个核心注解的作用机制&#xff1…

怎么检查网站CDN缓存是否生效

为什么要使用CDN缓存&#xff1f; 网站使用缓存可显著提升加载速度&#xff0c;减少服务器负载和带宽消耗&#xff0c;优化用户体验&#xff0c;增强架构稳定性&#xff0c;助力SEO优化&#xff0c;实现资源高效利用与性能平衡。 通过合理配置 CDN 缓存策略&#xff0c;可降低…

【自然语言处理】深度学习中文本分类实现

文本分类是NLP中最基础也是应用最广泛的任务之一&#xff0c;从无用的邮件过滤到情感分析&#xff0c;从新闻分类到智能客服&#xff0c;都离不开高效准确的文本分类技术。本文将带您全面了解文本分类的技术演进&#xff0c;从传统机器学习到深度学习&#xff0c;手把手实现一套…

vba讲excel转换为word

VBA将excel转换为word Sub ExportToWordFormatted() 声明变量Dim ws As Worksheet 用于存储当前活动的工作表Dim rng As Range 用于存储工作表的使用范围&#xff08;即所有有数据的单元格&#xff09;Dim rowCount As Long, colCount As Long 用于存储数据范围的行数和列数…

ubuntu安装openWebUI和Dify【自用详细版】

系统版本&#xff1a;ubuntu24.04LTS 显卡&#xff1a;4090 48G 前期准备 先安装好docker和docker-compose&#xff0c;可以参考我之前文章安装&#xff1a; ubuntu安装docker和docker-compose【简单详细版】 安装openWebUI 先docker下载ollama docker pull ghcr.nju.edu.c…

基于Flask的勒索病毒应急响应平台架构设计与实践

基于Flask的勒索病毒应急响应平台架构设计与实践 序言&#xff1a;安全工程师的防御视角 作为从业十年的网络安全工程师&#xff0c;我深刻理解勒索病毒防御的黄金时间法则——应急响应速度每提升1分钟&#xff0c;数据恢复成功率将提高17%。本文介绍的应急响应平台&#xff…

spark数据清洗案例:流量统计

一、项目背景 在互联网时代&#xff0c;流量数据是反映用户行为和业务状况的重要指标。通过对流量数据进行准确统计和分析&#xff0c;企业可以了解用户的访问习惯、业务的热门程度等&#xff0c;从而为决策提供有力支持。然而&#xff0c;原始的流量数据往往存在格式不规范、…

list的使用以及模拟实现

本章目标 1.list的使用 2.list的模拟实现 1.list的使用 在stl中list是一个链表,并且是一个双向带头循环链表,这种结构的链表是最优结构. 因为它的实现上也是一块线性空间,它的使用上是与string和vector类似的.但相对的因为底层物理结构上它并不像vector是线性连续的,它并没有…

【今日三题】小乐乐改数字 (模拟) / 十字爆破 (预处理+模拟) / 比那名居的桃子 (滑窗 / 前缀和)

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;每日两三题 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 小乐乐改数字 (模拟)十字爆破 (预处理模拟&#xff09;比那名居的桃子 (滑窗 / 前缀和) 小乐乐改数字 (模拟) 小乐乐改数字…

基于 Qt 的图片处理工具开发(一):拖拽加载与基础图像处理功能实现

一、引言 在桌面应用开发中&#xff0c;图片处理工具的核心挑战在于用户交互的流畅性和异常处理的健壮性。本文以 Qt为框架&#xff0c;深度解析如何实现一个支持拖拽加载、亮度调节、角度旋转的图片处理工具。通过严谨的文件格式校验、分层的架构设计和用户友好的交互逻辑&am…

44、Spring Boot 详细讲义(一)

Spring Boot 详细讲义 目录 Spring Boot 简介Spring Boot 快速入门Spring Boot 核心功能Spring Boot 技术栈与集成Spring Boot 高级主题Spring Boot 项目实战Spring Boot 最佳实践总结 一、Spring Boot 简介 1. Spring Boot 概念和核心特点 1.1、什么是 Spring Boot&#…

虽然理解git命令,但是我选择vscode插件!

文章目录 2025/3/11 补充一个项目一个窗口基本操作注意 tag合并冲突已有远程&#xff0c;新加远程仓库切换分支stash 只要了解 git 的小伙伴&#xff0c;应该都很熟悉这些指令&#xff1a; git init – 初始化git仓库git add – 把文件添加到仓库git commit – 把文件提交到仓库…

idea 打不开terminal

IDEA更新到2024.3后Terminal终端打不开的问题_idea terminal打不开-CSDN博客

【JVM】JVM调优实战

&#x1f600;大家好&#xff0c;我是白晨&#xff0c;一个不是很能熬夜&#x1f62b;&#xff0c;但是也想日更的人✈。如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下&#x1f440;白晨吧&#xff01;你的支持就是我最大的动力&#xff01;&#x1f4…

FPGA_DDR(二)

在下板的时候遇到问题 1&#xff1a;在写一包数据后再读&#xff0c;再写再读 这时候读无法读出 查看时axi_arready没有拉高 原因 &#xff1a; 由于读地址后没有拉高rready,导致数据没有读出卡死现象。 解决结果

【吾爱出品】[Windows] 鼠标或键盘可自定义可同时多按键连点工具

[Windows] 鼠标或键盘连点工具 链接&#xff1a;https://pan.xunlei.com/s/VONSFKLNpyVDeYEmOCBY3WZJA1?pwduik5# [Windows] 鼠标或键盘可自定义可同时多按键连点工具 就是个连点工具&#xff0c;功能如图所示&#xff0c;本人系统win11其他系统未做测试&#xff0c;自己玩…

vue3实战一、管理系统之实战立项

目录 管理系统之实战立项对应相关文章链接入口&#xff1a;实战效果登录页&#xff1a;动态菜单&#xff1a;动态按钮权限白天黑夜模式&#xff1a;全屏退出全屏退出登录&#xff1a;菜单收缩&#xff1a; 管理系统之实战立项 vue3实战一、管理系统之实战立项&#xff1a;这个项…