You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++ Mat与C# Bitmap互转异常:预处理后图像偏移重复问题

C#与C++ OpenCV图像转换偏移重复问题解决

问题背景

我们开发了一款C#控制台应用,负责将图像传入C++ DLL;C++ DLL会将图像转换为Mat格式并执行图像预处理,之后再将图像返回至C#控制台应用。目前面临的问题是:无法将预处理后的图像正确转换为Bitmap,转换后的图像出现偏移且字符重复。

错误分析

C++ DLL端问题

  1. 返回错误的图像数据:预处理完成后,代码返回的是原始输入图像lImage的字节数据,而非处理后的lOutImage,等于没有传递预处理结果。
  2. 尺寸与通道计算错误:计算返回数据大小时使用的是原始图像lImage的尺寸和通道数(3通道),但lOutImage是单通道灰度图,需要用lOutImage.total() * lOutImage.elemSize()计算正确字节数。
  3. 内存泄漏风险:使用new分配的raw_bytes没有提供释放逻辑,会导致内存泄漏。

C#端问题

  1. Bitmap转换逻辑完全错误:IntPtrToBitmap方法中存在无效操作,比如创建临时byte数组拷贝、错误将源数据覆盖为黑色,完全没有正确使用传入的图像数据。
  2. 格式不匹配:C++返回的是单通道灰度图数据,但C#端创建的是32位ARGB格式Bitmap,直接按4通道处理会导致数据错位。
  3. 忽略Stride对齐:Bitmap的行数据存在内存对齐(Stride),直接按width*4计算行偏移会导致图像偏移。

修正后的代码

C++ DLL代码

// 添加内存释放函数供C#调用
extern "C" __declspec(dllexport) void FreeImageBytes(unsigned char* data)
{
    delete[] data;
}

extern "C" __declspec(dllexport) unsigned char* PreProcessImage(unsigned char* img, long data_len, int* bytes_count, int* out_width, int* out_height, int iScaleFactor, int iErodeValue, int iDilateValue, int iSmoothValue, int iThreshType, int iThreshMinValue, int iThreshMaxValue, bool IsWhiteOnBlack)
{
    vector<unsigned char> inputImageBytes(img, img + data_len);
    cv::Mat mat = imdecode(inputImageBytes, IMREAD_COLOR);
    if (mat.empty())
    {
        *bytes_count = 0;
        *out_width = 0;
        *out_height = 0;
        return nullptr;
    }

    cv::Mat grayImage;
    cv::cvtColor(mat, grayImage, cv::COLOR_BGR2GRAY);

    int iImageWidth = grayImage.cols;
    int iImageHeight = grayImage.rows;
    double multipleFactor = 1; 
    // 确保MultipleFactorValue函数正确设置multipleFactor值
    MultipleFactorValue(iScaleFactor);

    cv::Size outputSize(static_cast<int>(iImageWidth * multipleFactor), static_cast<int>(iImageHeight * multipleFactor));
    cv::Mat outputImage;
    cv::resize(grayImage, outputImage, outputSize);

    cv::Mat lOutImage;
    // 高斯模糊
    if (iSmoothValue > 0) {
        cv::GaussianBlur(outputImage, outputImage, cv::Size(iSmoothValue, iSmoothValue), 0, 0);
    }

    // 阈值处理
    if (iThreshType == 0) {
        cv::threshold(outputImage, lOutImage, iThreshMinValue, iThreshMaxValue, cv::THRESH_BINARY);
    }
    else {
        cv::adaptiveThreshold(outputImage, lOutImage, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, 3, 0);
    }

    // 腐蚀操作
    cv::Mat se = cv::getStructuringElement(cv::MORPH_CROSS, cv::Size(3, 3));
    cv::Scalar borderValue = cv::Scalar(255);
    if (iErodeValue > 0) {
        cv::erode(lOutImage, lOutImage, se, cv::Point(-1, -1), iErodeValue, 0, borderValue);
    }

    // 膨胀操作
    if (iDilateValue > 0) {
        cv::dilate(lOutImage, lOutImage, se, cv::Point(-1, -1), iDilateValue, 0, borderValue);
    }

    if (IsWhiteOnBlack) {
        cv::bitwise_not(lOutImage, lOutImage);
    }

    // 返回处理后的图像尺寸
    *out_width = lOutImage.cols;
    *out_height = lOutImage.rows;
    // 计算单通道图像的总字节数
    int size = lOutImage.total() * lOutImage.elemSize();
    unsigned char* raw_bytes = new unsigned char[size];
    memcpy(raw_bytes, lOutImage.data, size);

    *bytes_count = size;
    return raw_bytes;
}

C#控制台代码

// 导入DLL函数
[DllImport("YourDllName.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr PreProcessImage(byte[] img, long data_len, out int bytes_count, out int out_width, out int out_height, int iScaleFactor, int iErodeValue, int iDilateValue, int iSmoothValue, int iThreshType, int iThreshMinValue, int iThreshMaxValue, bool IsWhiteOnBlack);

[DllImport("YourDllName.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void FreeImageBytes(IntPtr data);

public static void CallingClass()
{
    string imagePath = "your_image_path.jpg";
    Bitmap bitmap = (Bitmap)Image.FromFile(imagePath);
    byte[] imageData = LoadImage(imagePath);

    IntPtr ptr = PreProcessImage(imageData, imageData.Length, out int bytes_count, out int out_width, out int out_height, 1, 0, 0, 0, 1, 70, 255, false);
    if (ptr == IntPtr.Zero || bytes_count == 0)
    {
        Console.WriteLine("预处理失败");
        return;
    }

    Bitmap img = GrayscalePtrToBitmap(ptr, out_width, out_height);

    // 保存处理后的图像
    img.Save("Output.bmp");
    img.Dispose();

    // 释放C++分配的内存
    FreeImageBytes(ptr);
    Console.ReadKey();
}

static Bitmap GrayscalePtrToBitmap(IntPtr ptr, int width, int height)
{
    // 创建8位灰度格式Bitmap
    Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
    // 设置灰度调色板
    ColorPalette palette = bitmap.Palette;
    for (int i = 0; i < 256; i++)
    {
        palette.Entries[i] = Color.FromArgb(i, i, i);
    }
    bitmap.Palette = palette;

    BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
    try
    {
        // 逐行拷贝数据,处理Stride对齐
        int sourceStride = width; // OpenCV单通道图像行字节数等于宽度
        int destStride = bitmapData.Stride;

        for (int y = 0; y < height; y++)
        {
            IntPtr sourceRow = ptr + y * sourceStride;
            IntPtr destRow = bitmapData.Scan0 + y * destStride;
            // 高效拷贝一行数据
            memcpy(destRow, sourceRow, width);
        }
    }
    finally
    {
        bitmap.UnlockBits(bitmapData);
    }

    return bitmap;
}

// 导入C标准库的memcpy函数
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void memcpy(IntPtr dest, IntPtr src, int count);

// 读取图像文件字节
static byte[] LoadImage(string path)
{
    return File.ReadAllBytes(path);
}

关键修正说明

  1. C++端:

    • 返回处理后的lOutImage数据,同时传递处理后的图像宽高给C#
    • 添加FreeImageBytes函数用于释放C++分配的内存
    • 修正字节数计算逻辑,使用单通道图像的尺寸
  2. C#端:

    • 重写图像转换函数,创建8位灰度格式Bitmap,匹配C++返回的单通道数据
    • 处理Bitmap的Stride对齐问题,逐行拷贝数据避免偏移
    • 调用FreeImageBytes释放C++内存,避免内存泄漏

内容的提问来源于stack exchange,提问作者J. Vas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 21:47:33