C#与Python脚本使用共享内存通信

实现的功能:

C#中读取一张图像,通过共享内存传给python脚本进行处理后将图像进行存储,C#读取处理过后的图像。

C#与python通信有好几种,为什么选择共享内存?

处理图像的速度需求是1秒钟处理5张以上,通过进程调用的方式无法达到速度的要求,共享内存可以。
优点是速度快,缺点是需要在运行的电脑上安装python环境。

好了,废话说完了,下面是代码示例

我使用的框架是Framework4.6.2,首先需要安装pythonnet NuGet包。框架依赖项显示需要.Net2.0及以上,实测Framework4.6.2也可以使用

实现源码:C#部分

csharp 复制代码
private void PythonInit()
{
    // 设置Python的安装目录
    string pathToVirtualEnv = @"C:\Users\worker\AppData\Local\Programs\Python\Python312";  // 替换为你自己的Python安装路径

    // 设置Python DLL和Python解释器的路径
    Runtime.PythonDLL = System.IO.Path.Combine(pathToVirtualEnv, "python312.dll");
    PythonEngine.PythonHome = System.IO.Path.Combine(pathToVirtualEnv, "python.exe");
    // 设置Python路径,包括必要的依赖库路径
    PythonEngine.PythonPath = "C:\\Users\\worker\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages;C:\\Users\\worker\\AppData\\Local\\Programs\\Python\\Python312\\Lib;C:\\Users\\worker\\AppData\\Local\\Programs\\Python\\Python312\\DLLs;C:\\Users\\worker\\AppData\\Local\\Programs\\Python\\Python312\\tcl";
    PythonEngine.Initialize();
}

private async void BtnMemoryTest_Click(object sender, EventArgs e)
{
    // 使用 Task.Run 启动异步任务
    await Task.Run(() =>
    {
        try
        {
            PythonInit();
            var stopwatch = new System.Diagnostics.Stopwatch();

            // 计时开始
            stopwatch.Start();

            // 加载图像并写入共享内存
            Bitmap b = new Bitmap("output.png");
            using (MemoryStream ms = new MemoryStream())
            {
                b.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
                byte[] bytes = ms.ToArray();  // 使用 ToArray 获取字节数组

                // 创建或打开共享内存
                using (var mmf = MemoryMappedFile.CreateOrOpen("test1", bytes.Length, MemoryMappedFileAccess.ReadWrite))
                {
                    using (var viewAccessor = mmf.CreateViewAccessor(0, bytes.Length))
                    {
                        viewAccessor.Write(0, bytes.Length);
                        viewAccessor.WriteArray<byte>(0, bytes, 0, bytes.Length);
                    }
                }

                // 显示写入成功
                this.Invoke(new Action(() => MessageBox.Show("Write ok, size: " + bytes.Length.ToString())));
            }

            // 在子线程中调用 Python 脚本
            using (Py.GIL())  // 必须获取 GIL 锁来执行 Python 脚本
            {
                string file = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "MemoryTest.py");
                using (var scope = Py.CreateScope())
                {
                    string code = File.ReadAllText(file);
                    var scriptCompiled = PythonEngine.Compile(code);
                    scope.Execute(scriptCompiled);
                }
            }

            // 计时结束
            stopwatch.Stop();
            this.Invoke(new Action(() => MessageBox.Show($"Execution completed in {stopwatch.ElapsedMilliseconds} ms")));
        }
        catch (Exception ex)
        {
            this.Invoke(new Action(() => MessageBox.Show("Error: " + ex.Message)));
        }
    });
}

Python源码:

python 复制代码
import mmap
import cv2
import numpy as np
import matplotlib
import matplotlib.pyplot as plt

# 使用非图形界面的后端
matplotlib.use('Agg')

# 共享内存的字节大小
byteSize = 40054

# 共享内存的名称
file_name = 'test1'

# 打开共享内存文件
f = mmap.mmap(0, byteSize, file_name, mmap.ACCESS_READ)

# 读取共享内存中的数据并解码为图像
img = cv2.imdecode(np.frombuffer(f, np.uint8), cv2.IMREAD_COLOR)

# 将图像转换为灰度图像
gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 平滑处理:应用高斯模糊
blurred_image = cv2.GaussianBlur(gray_image, (5, 5), 0)

# 获取图像的尺寸
height, width = gray_image.shape

# 创建网格数据,代表图像中的坐标点
X, Y = np.meshgrid(np.arange(0, width), np.arange(0, height))

# 创建一个图形,并在其上叠加等值线
plt.figure()

# 保存图像为文件
output_file = 'output_image.png'
plt.savefig(output_file, bbox_inches='tight', pad_inches=0)

# 关闭绘图窗口
plt.close()

print(f"Image saved successfully to {output_file}")

处理时遇到的问题

1、使用之前需要进行PythonEngine的初始化,且只需要初始化一次

所以BtnMemoryTest点击第一次可以成功运行,第二次的时候会报错{"This property must be set before runtime is initialized"}
为什么我要在异步中初始化,而不是在窗口加载时进行初始化?

因为Py.GIL()必须获取 GIL 锁来执行 Python 脚本,如果PythonEngine的初始化和Py.GIL()不在同一个线程ID中,Py.GIL()会一直获取不到GIL锁,导致程序卡住。

所以如果在线程中调用Python脚本,一定要在同线程中对PythonEngine进行初始化。
2、注意共享内存大小的修改

Python源码中byteSize = 40054,这个是需要根据你图像的大小去修改的,在C#源码中this.Invoke(new Action(() => MessageBox.Show("Write ok, size: " + bytes.Length.ToString())));会弹窗提示,根据bytes.Length进行修改即可。
3、运行环境配置

确保你的电脑已经将python添加到系统环境变量。如果报错:No module named 'xxx' ,是缺少了DLLs文件夹的路径,在安装目录下找到xxx的路径,在PythonEngine.PythonPath添加"xxx"的路径即可。
运行耗时

第一次运行会耗时长一点,100-300ms都是正常的,再次运行耗时很短,约几毫秒左右。

相关推荐
爱睡懒觉的焦糖玛奇朵几秒前
【工业级落地算法之打架斗殴检测算法详解】
人工智能·python·深度学习·学习·算法·yolo·计算机视觉
rOuN STAT1 分钟前
PLC(电力载波通信)网络机制介绍
开发语言·网络·php
wjs20241 分钟前
HTML 标签列表(功能排序)
开发语言
深挖派3 分钟前
PyCharm 2026.1 全版本安装配置与全功能环境搭建 (保姆级图文教程)
ide·python·pycharm
无尽的罚坐人生3 分钟前
hot 100 146. LRU 缓存
java·开发语言·缓存
好家伙VCC4 分钟前
**发散创新:基于算子融合的深度学习推理优化实战**在现代AI部署场景
java·人工智能·python·深度学习
Ofm1z1Q9R4 分钟前
python-langchain框架(3-5-pdf文件load_and_split()加载 )
python·langchain·pdf
We་ct5 分钟前
JS手撕:DOM操作 & 浏览器API高频场景详解
开发语言·前端·javascript·面试·状态模式·操作·考点
wd5i8kA8i7 分钟前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
java·开发语言·php
数据知道12 分钟前
claw-code 源码详细分析:命令宇宙 vs 工具宇宙——`commands` / `tools` 镜像清单如何驱动路由与 shim 执行?
linux·服务器·网络·python·ai·claude code