C++ Mat与C# Bitmap互转异常:预处理后图像偏移重复问题
C#与C++ OpenCV图像转换偏移重复问题解决
问题背景
我们开发了一款C#控制台应用,负责将图像传入C++ DLL;C++ DLL会将图像转换为Mat格式并执行图像预处理,之后再将图像返回至C#控制台应用。目前面临的问题是:无法将预处理后的图像正确转换为Bitmap,转换后的图像出现偏移且字符重复。
错误分析
C++ DLL端问题
- 返回错误的图像数据:预处理完成后,代码返回的是原始输入图像
lImage的字节数据,而非处理后的lOutImage,等于没有传递预处理结果。 - 尺寸与通道计算错误:计算返回数据大小时使用的是原始图像
lImage的尺寸和通道数(3通道),但lOutImage是单通道灰度图,需要用lOutImage.total() * lOutImage.elemSize()计算正确字节数。 - 内存泄漏风险:使用
new分配的raw_bytes没有提供释放逻辑,会导致内存泄漏。
C#端问题
- Bitmap转换逻辑完全错误:
IntPtrToBitmap方法中存在无效操作,比如创建临时byte数组拷贝、错误将源数据覆盖为黑色,完全没有正确使用传入的图像数据。 - 格式不匹配:C++返回的是单通道灰度图数据,但C#端创建的是32位ARGB格式Bitmap,直接按4通道处理会导致数据错位。
- 忽略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); }
关键修正说明
C++端:
- 返回处理后的
lOutImage数据,同时传递处理后的图像宽高给C# - 添加
FreeImageBytes函数用于释放C++分配的内存 - 修正字节数计算逻辑,使用单通道图像的尺寸
- 返回处理后的
C#端:
- 重写图像转换函数,创建8位灰度格式Bitmap,匹配C++返回的单通道数据
- 处理Bitmap的Stride对齐问题,逐行拷贝数据避免偏移
- 调用
FreeImageBytes释放C++内存,避免内存泄漏
内容的提问来源于stack exchange,提问作者J. Vas
相关产品推荐
相关产品推荐

