这段代码是一个使用 Halcon 图像处理库进行条形码扫描的类实现

这段代码是一个使用 Halcon 图像处理库进行条形码扫描的类实现,名为 `GeneralBarcodeScan`。它包含了多个方法来初始化相机、捕获图像、检测条形码以及在窗口中显示图像。以下是对代码的详细分析:

### 1. **类结构与主要功能**
   - **类名**: `GeneralBarcodeScan`
   - **功能**: 该类主要用于通过 Halcon 库进行条形码扫描,支持单相机和双相机模式。主要功能包括:
     - 初始化相机并打开图像采集。
     - 捕获图像并显示在窗口中。
     - 检测图像中的条形码。
     - 通过回调接口返回扫描到的条形码数据。
     - 支持双相机模式,能够同时处理两台相机的图像。

### 2. **主要成员变量**
   - `mLastErrorMsg`: 保存最后的错误信息。
   - `mCallback`: 相机回调接口,用于处理扫描到的条形码。
   - `mHvAcqHandle` 和 `mHvAcqHandle2`: 相机句柄,用于控制相机的图像捕获。
   - `mHoImage` 和 `mHoImage2`: 捕获的图像对象。
   - `mWndWidth` 和 `mWndHeight`: 窗口的宽度和高度。
   - `mHPreviewWnd`: Halcon 图像显示窗口。
   - `mHvWindowHandle` 和 `mHvWindowHandle2`: 窗口句柄,用于控制图像显示。

### 3. **主要方法**
   - **`OpenImageAcq`**: 打开相机并初始化图像采集。支持单相机和双相机模式。
     - 通过 `InfoFramegrabber` 获取相机设备信息。
     - 使用 `OpenFramegrabber` 打开相机并设置采集参数。
     - 打开显示窗口并设置窗口句柄。
   - **`CloseImageAcq`**: 关闭相机并释放资源,确保相机和图像对象被正确清理。
   - **`GrabImage`**: 异步捕获图像并显示在窗口中。
     - 使用 `GrabImageAsync` 捕获图像。
     - 通过 `GetImageSize` 获取图像尺寸。
     - 使用 `DispImage` 显示图像。
   - **`StartScanBarcode`**: 执行条形码扫描。
     - 创建条形码检测模型并设置参数。
     - 使用 `FindBarCode` 查找图像中的条形码。
     - 将扫描到的条形码数据通过回调接口返回。
   - **`StartScanBarcodeBy2Device`**: 使用两台相机进行条形码扫描。
     - 捕获两台相机的图像并拼接。
     - 使用 `FindBarCode` 查找拼接图像中的条形码。
     - 将扫描到的条形码数据通过回调接口返回。
   - **`GetDisplayRect`**: 计算显示区域的矩形,确保图像按比例适应窗口尺寸。

### 4. **回调机制**
   - 通过 `ICcdCallback` 接口,当扫描到条形码时,调用 `FoundBarcode` 方法将扫描到的条形码数据返回给外部使用者。

### 5. **资源管理**
   - 实现了 `IDisposable` 接口,确保在对象销毁时释放相机和图像资源,防止内存泄漏。
   - 在 `CloseImageAcq` 方法中,显式释放了相机句柄、图像对象和窗口句柄。

### 6. **双相机支持**
   - 代码支持双相机模式,能够同时捕获两台相机的图像并拼接显示。
   - 在 `StartScanBarcodeBy2Device` 方法中,通过 `ConcatObj` 和 `TileImages` 合并两张图像,并根据 `revease` 参数决定图像的拼接顺序。

### 7. **图像显示与比例调整**
   - 通过 `GetDisplayRect` 方法计算图像显示区域,确保图像按比例适应窗口尺寸,避免图像被拉伸或压缩。

### 8. **可能的改进建议**
   - **错误处理**: 当前的错误处理是通过字符串 `mLastErrorMsg` 来记录错误,建议增加更多详细的错误日志和异常抛出机制,以便于调试和定位问题。
   - **资源释放**: 虽然代码中已经处理了大部分资源的释放,但在某些方法中(例如 `StartScanBarcode`),可以进一步确保所有 `HObject` 类型的变量在使用完之后都被明确释放,以避免内存泄漏。
   - **并行处理**: 如果对条形码扫描有高效需求,可以考虑使用多线程或异步方法对多相机模式进行优化,以提高系统的性能和响应速度。

### 9. **总结**
   - 这段代码是一个功能较为完整的条形码扫描工具,结合了 Halcon 库的强大图像处理能力,支持单相机和双相机模式。
   - 代码结构清晰,功能模块化,便于扩展和维护。
   - 通过回调机制,能够将扫描到的条形码数据返回给外部使用者,适用于需要实时处理条形码的应用场景。

如果你有具体的需求或问题,可以进一步讨论如何优化或扩展这段代码。

/*
 这段代码是一个通过 Halcon 图像处理库进行条形码扫描的类实现。类名为
 GeneralBarcodeScan,包含多个方法来初始化相机、捕获图像、检测条形码以
及在窗口中显示图像。接下来,我将详细解释代码的功能并在每一部分添加注释。
*/

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;

        /// <summary>
        /// 释放资源,关闭相机及图像对象
        /// </summary>
        public void Dispose()
        {
            CloseImageAcq();
        }

        /// <summary>
        /// 构造函数,初始化相机句柄和图像对象
        /// </summary>
        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;
        }

        /// <summary>
        /// 设置回调接口
        /// </summary>
        /// <param name="callback">回调接口实例</param>
        public void setCallback(ICcdCallback callback)
        {
            this.mCallback = callback;
        }

        /// <summary>
        /// 打开相机并初始化图像采集
        /// </summary>
        /// <param name="hHalconWnd">Halcon 窗口</param>
        /// <param name="wndWidth">窗口宽度</param>
        /// <param name="wndHeight">窗口高度</param>
        /// <param name="deviceQty">相机数量</param>
        /// <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;
        }

        /// <summary>
        /// 关闭相机并释放资源
        /// </summary>
        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;
            }
        }

        /// <summary>
        /// 获取最后的错误信息
        /// </summary>
        /// <returns>最后的错误信息</returns>
        public string GetLastErrorMsg()
        {
            return this.mLastErrorMsg;
        }

        /// <summary>
        /// 异步捕获图像并显示在窗口
        /// </summary>
        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();
            }
        }

        /// <summary>
        /// 执行条形码扫描
        /// </summary>
        /// <param name="qty">相机数量</param>
        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);
            }
        }

        /// <summary>
        /// 使用两台相机进行条形码扫描
        /// </summary>
        /// <param name="revease">是否反转图像顺序</param>
        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 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 };
        }
    }
}

代码注释总结:

  • 类与方法注释: 每个方法和类都有详细的注释,解释了其功能、参数和返回值。

  • 关键代码块注释: 在关键代码块(如相机初始化、图像捕获、条形码检测等)中添加了注释,帮助理解代码的逻辑。

  • 资源管理注释: 在资源释放的代码块中添加了注释,确保资源的正确释放,避免内存泄漏。

通过这些注释,代码的可读性和可维护性得到了显著提升。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小范好好学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值