C#中Cv2的Mat与byteString相互转换

最近有项目要使用GRPC在C#和Python间进行图像传送。由于对C#不熟,想找到一个示例,网上资料不多,大模型给出示例不能正常运行,在c#中对图像的编解码方法摸索花了较长时间。解决之道还是查看文档。找到简洁的方法,记录供日后使用。

编码

在使用GRPC传送图像前,对于文件读取的二进制图像可以直接传送。对于内存中的Mat图像,需要对图像编码转换。先将Mat转换为byteArray,再将byteArray转换为byteString才能发送。这是protobuf的byte类型对应C#的byteString类型,否则导致类型不匹配。

// 读取图像
Mat image = Cv2.imread(“filepath.jpg”)

// 将Mat转换为字节数组
byte[] byteArray = image.ToBytes();


// 转换为byteString
ByteString byteString = ByteString.CopyFrom(byteArray);


// 发送Grpc数据
var reply = client.SendImage(new RequestData {Name= userInput, Photo = byteString} );

解码

对于从GRPC接收到的二进制图像数据,其编码方式是ByteString,先将其转化为byteArray数组,再使用Cv2的ImDecode方法解码成图像。网络上的方法大致分两种,一种是通过内存读写的方式转换,另一种是创建Mat图像,使用Marshal.copy()方法。这两种方法都未能正常运行。下面的方式简洁有效:

ByteString byteString = request.Photo;
byte[] byteArray = byteString.ToByteArray();
Mat image = Cv2.ImDecode(byteArray, ImreadModes.Color);
Cv2.ImShow("received from client", image);

<think>我们正在处理一个跨语言(C#和C++)的图像数据传输问题,涉及两个主要步骤: 1. 在C#中将Bitmap对象转换为指针(以便传递给C++进程) 2. 在C++中将接收到的图像指针转换OpenCVMat对象 同时,由于是进程间通信(Inter-Process Communication, IPC),我们需要考虑如何传递这个指针以及相关的图像信息(如宽度、高度、通道数等)。 ### 步骤1:C# 中将 Bitmap 转换为指针 在C#中,我们可以使用`Bitmap.LockBits`方法锁定位图数据,然后获取指向图像数据的指针。 ```csharp using System.Drawing; using System.Drawing.Imaging; public class ImageConverter { public static IntPtr ConvertBitmapToPointer(Bitmap bitmap, out int width, out int height, out int channels, out int stride) { // 获取图像尺寸 width = bitmap.Width; height = bitmap.Height; // 锁定位图到系统内存 BitmapData bmpData = bitmap.LockBits( new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, bitmap.PixelFormat ); // 获取图像数据指针 IntPtr ptr = bmpData.Scan0; stride = bmpData.Stride; // 计算通道数(根据PixelFormat) channels = Image.GetPixelFormatSize(bitmap.PixelFormat) / 8; // 注意:这里不能解锁,因为后面还要使用指针。所以需要调用者在适当时候解锁。 // 但是,我们这里不能解锁,所以需要调用者记住这个BitmapData,并在使用完后解锁。 // 因此,我们可能需要返回BitmapData,但为了简化,我们只返回指针,并让调用者负责解锁。 // 所以,我们这里不调用UnlockBits,而是由调用者负责。 // 返回指针,同时返回图像信息 return ptr; } // 提供一个方法用于解锁位图 public static void UnlockBitmap(Bitmap bitmap, BitmapData bmpData) { bitmap.UnlockBits(bmpData); } } ``` 注意:调用`ConvertBitmapToPointer`后,位图被锁定,直到调用`UnlockBitmap`。因此,在将图像数据传递给C++并处理完成后,必须解锁位图。 ### 步骤2:进程间传递指针和图像信息 由于是进程间通信,我们不能直接传递指针(因为每个进程有自己的地址空间,指针在另一个进程中无效)。因此,我们需要使用共享内存(Shared Memory)来传递图像数据。 #### 共享内存方案 1. 在C#和C++之间建立共享内存区域。 2. C#将图像数据复制到共享内存。 3. C++从共享内存中读取图像数据。 这样,我们就不需要传递指针,而是传递共享内存的名称(或句柄)以及图像信息(宽度、高度、通道数、步长等)。 ### 步骤3:C++ 中将图像数据指针转换Mat 在C++端,我们假设已经通过共享内存获取了图像数据(即一个指向图像数据的指针)以及图像信息。 ```cpp #include <opencv2/opencv.hpp> // 假设我们有一个指向图像数据的指针,以及图像信息 void ConvertPointerToMat(unsigned char* imageData, int width, int height, int channels, int stride, cv::Mat& outputMat) { // 注意:OpenCVMat要求数据是连续的,但有时图像可能有步长(stride)不等于width*channels(即每行可能有填充字节) // 因此,我们需要考虑步长 // 如果步长等于width*channels,则数据连续,可以直接构造Mat if (stride == width * channels) { outputMat = cv::Mat(height, width, CV_8UC(channels), imageData); } else { // 如果不连续,我们需要逐行拷贝 outputMat = cv::Mat::zeros(height, width, CV_8UC(channels)); for (int i = 0; i < height; i++) { // 拷贝一行:源指针当前行起始位置,目标Mat的当前行起始位置 memcpy(outputMat.ptr(i), imageData + i * stride, width * channels); } } // 注意:这里创建的Mat不负责释放imageData,因为数据来自共享内存,需要另外处理 // 因此,在共享内存释放前,不能销毁Mat。或者使用拷贝的方式(如上述不连续的情况)则没有这个问题。 } ``` ### 完整流程 1. C#端: - 将`Bitmap`锁定,获取指针和图像信息。 - 创建或打开共享内存,将图像数据复制到共享内存。 - 将共享内存的名称(或句柄)以及图像信息(宽、高、通道数、步长)通过某种IPC机制(如命名管道、Socket、消息队列等)传递给C++进程。 - 等待C++处理完成,然后解锁位图并释放共享内存。 2. C++端: - 接收共享内存名称和图像信息。 - 打开共享内存,获取指向图像数据的指针。 - 使用`ConvertPointerToMat`函数将图像数据转换为`cv::Mat`。 - 处理图像。 - 关闭共享内存。 ### 注意事项 - **共享内存同步**:在读写共享内存时,需要使用同步机制(如互斥锁、信号量)来防止竞争条件。 - **数据格式**:确保C#中的`PixelFormat`C++中Mat的通道数匹配。例如,如果C#中是`Format24bppRgb`,则通道数为3,C++中应使用`CV_8UC3`。 - **内存管理**:共享内存的生命周期需要仔细管理,确保在双方都使用完毕后再释放。 ### 示例代码(简化版) #### C#端(使用MemoryMappedFile) ```csharp using System; using System.Drawing; using System.Drawing.Imaging; using System.IO.MemoryMappedFiles; public class SharedMemoryImageSender { public void SendImage(Bitmap bitmap) { int width, height, channels, stride; IntPtr ptr = ImageConverter.ConvertBitmapToPointer(bitmap, out width, out height, out channels, out stride); // 计算共享内存大小(注意:stride * height) int size = stride * height; // 创建共享内存 string mapName = "MyImageSharedMemory"; using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew(mapName, size)) using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor()) { // 将图像数据复制到共享内存 unsafe { byte* src = (byte*)ptr.ToPointer(); byte* dest = null; accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref dest); for (int i = 0; i < height; i++) { Buffer.MemoryCopy(src + i * stride, dest + i * stride, stride, stride); } accessor.SafeMemoryMappedViewHandle.ReleasePointer(); } // 通过另一个IPC机制(如管道)发送mapName和图像信息(宽、高、通道数、步长) // 例如:SendViaPipe(mapName, width, height, channels, stride); // 等待C++处理完成(根据具体IPC机制) } // 解锁位图 ImageConverter.UnlockBitmap(bitmap, ...); // 需要保存之前的BitmapData } } ``` #### C++端(使用共享内存) ```cpp #include <windows.h> #include <opencv2/opencv.hpp> int main() { // 从IPC接收共享内存名称和图像信息 std::string mapName = ...; int width = ..., height = ..., channels = ..., stride = ...; // 打开共享内存 HANDLE hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapName.c_str()); if (hMapFile == NULL) return -1; // 映射视图 unsigned char* pBuf = (unsigned char*)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0); if (pBuf == NULL) { CloseHandle(hMapFile); return -2; } // 转换Mat cv::Mat image; ConvertPointerToMat(pBuf, width, height, channels, stride, image); // 处理图像... // 清理 UnmapViewOfFile(pBuf); CloseHandle(hMapFile); return 0; } ``` ### 相关问题 1. 在C#中,如何安全地处理位图数据以避免内存泄漏? 2. 在C++中使用OpenCVMat时,如何避免数据拷贝以提高性能? 3. 除了共享内存,还有哪些进程间通信方式适合传输图像数据? 4. 如何处理不同像素格式(如32位ARGB和24位RGB)之间的转换? 5. 在跨进程图像传输中,如何确保数据的实时性和同步?</think>### C# Bitmap 指针 C++ 指针 OpenCV Mat 的跨进程图像传输方案 #### 核心步骤概览 1. **C#**:将 `Bitmap` 锁定到内存,获取图像数据指针 2. **跨进程通信**:通过共享内存传递指针和图像元数据 3. **C++ 端**:将接收的指针转换OpenCV `Mat` 对象 --- ### 一、C# Bitmap 指针实现 ```csharp using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; public unsafe class BitmapConverter { public static IntPtr ConvertToPointer(Bitmap bitmap, out int width, out int height, out int channels) { // 锁定位图到系统内存 BitmapData bmpData = bitmap.LockBits( new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat ); // 获取图像元数据 width = bitmap.Width; height = bitmap.Height; channels = Image.GetPixelFormatSize(bitmap.PixelFormat) / 8; // 计算通道数 // 返回指向图像数据的指针 return bmpData.Scan0; } // 解锁位图 (传输完成后调用) public static void ReleaseBitmap(Bitmap bitmap, BitmapData bmpData) { bitmap.UnlockBits(bmpData); } } ``` #### 关键说明: 1. `ImageLockMode.ReadOnly` 确保原始数据不被修改 2. 像素格式需匹配: - 24bpp: `PixelFormat.Format24bppRgb` → C++ 端用 `CV_8UC3` - 32bpp: `PixelFormat.Format32bppArgb` → C++ 端用 `CV_8UC4` 3. 通道计算:`PixelFormat` 的位深度除以 8 得到字节数/像素 --- ### 二、跨进程通信(共享内存方案) #### C# 共享内存写入 (使用 MemoryMappedFile) ```csharp using System.IO.MemoryMappedFiles; // 创建共享内存 using (var mmf = MemoryMappedFile.CreateNew("Global/ImageData", dataSize)) { // 写入图像元数据 (宽度/高度/通道数) using (var accessor = mmf.CreateViewAccessor(0, sizeof(int) * 3)) { accessor.Write(0, width); accessor.Write(sizeof(int), height); accessor.Write(sizeof(int)*2, channels); } // 写入图像像素数据 using (var stream = mmf.CreateViewStream(sizeof(int)*3, dataSize)) { byte* ptr = (byte*)bitmapPtr.ToPointer(); for (int y = 0; y < height; y++) { stream.Write(new ReadOnlySpan<byte>(ptr + y * stride, width * channels)); } } } ``` #### C++ 共享内存读取 (Windows API) ```cpp #include <windows.h> HANDLE hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"Global/ImageData"); LPVOID pBuf = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0); // 读取元数据 int width = *static_cast<int*>(pBuf); int height = *(static_cast<int*>(pBuf) + 1); int channels = *(static_cast<int*>(pBuf) + 2); // 获取像素数据指针 uchar* imageData = static_cast<uchar*>(pBuf) + sizeof(int) * 3; ``` --- ### 三、C++ 指针 OpenCV Mat ```cpp #include <opencv2/opencv.hpp> cv::Mat PointerToMat(uchar* data, int width, int height, int channels) { // 根据通道数确定Mat类型 int matType = (channels == 1) ? CV_8UC1 : (channels == 3) ? CV_8UC3 : CV_8UC4; // 创建Mat对象 (不复制数据) return cv::Mat(height, width, matType, data); } // 使用示例: cv::Mat image = PointerToMat(imageData, width, height, channels); ``` #### 重要注意事项: 1. **内存生命周期**: - C# 端需保持 `Bitmap` 锁定状态直到 C++ 完成处理 - 使用引用计数或信号量同步释放时机 2. **跨平台兼容性**: - Linux/Mac 使用 `shm_open()` 替代 `CreateFileMapping` 3. **性能优化**: - 使用环形缓冲区实现双缓冲机制 - 压缩传输 (JPEG) 减少数据量 --- ### 四、完整工作流程 1. C# 锁定 Bitmap → 获取指针 2. 通过共享内存传递: - 元数据 (宽度、高度、通道数) - 像素数据 3. C++ 创建非拷贝 Mat: ```cpp cv::Mat receivedImage(height, width, CV_8UC(channels), sharedMemoryPtr); ``` 4. 处理完成后: - C++ 发送完成信号 - C# 解锁 Bitmap --- ### 常见问题解决方案 1. **像素格式不匹配**: - 在 C# 端统一转换24/32bpp: ```csharp new Bitmap(source, width, height, PixelFormat.Format24bppRgb); ``` 2. **内存对齐问题**: - 使用 `Mat::step` 处理 stride: ```cpp cv::Mat matWithStride(height, width, type, data, stride); ``` 3. **跨进程同步**: ```csharp // C# 使用 EventWaitHandle using (var readyEvent = new EventWaitHandle(false, EventResetMode.AutoReset, "ImageReadyEvent")) { readyEvent.Set(); // 通知C++数据就绪 } ``` --- ### 性能优化建议 1. **零拷贝传输**:直接共享 GPU 显存 (DX11/D3D11 互操作) 2. **异步管道**: ```cpp // C++ 端使用 boost::interprocess managed_shared_memory segment(open_only, "ImageBuffer"); ``` 3. **压缩传输**: ```csharp // C# 使用 JPEG 压缩 bitmap.Save(stream, ImageFormat.Jpeg, compressionParams); ``` --- ### 相关问题 1. 如何处理不同像素格式(如 `Format32bppArgb`)在 OpenCV 中的转换2. 如何实现 C++ 处理结果返回到 C# 的零拷贝传输? 3. 在跨平台(Windows/Linux)场景下,共享内存的实现有哪些差异? 4. 如何通过 GPU 加速(如 CUDA)实现跨进程图像处理管线? 5. 当传输高分辨率图像(4K/8K)时,如何避免内存复制导致的性能瓶颈?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值