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都是正常的,再次运行耗时很短,约几毫秒左右。

相关推荐
hccee22 分钟前
C# IO文件操作
开发语言·c#
hummhumm26 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
杜小满31 分钟前
周志华深度森林deep forest(deep-forest)最新可安装教程,仅需在pycharm中完成,超简单安装教程
python·随机森林·pycharm·集成学习
J老熊36 分钟前
JavaFX:简介、使用场景、常见问题及对比其他框架分析
java·开发语言·后端·面试·系统架构·软件工程
zmd-zk1 小时前
flink学习(2)——wordcount案例
大数据·开发语言·学习·flink
好奇的菜鸟1 小时前
Go语言中的引用类型:指针与传递机制
开发语言·后端·golang
Alive~o.01 小时前
Go语言进阶&依赖管理
开发语言·后端·golang
花海少爷1 小时前
第十章 JavaScript的应用课后习题
开发语言·javascript·ecmascript
手握风云-1 小时前
数据结构(Java版)第二期:包装类和泛型
java·开发语言·数据结构
databook1 小时前
『玩转Streamlit』--布局与容器组件
python·机器学习·数据分析