实现的功能:
C#中读取一张图像,通过共享内存传给python脚本进行处理后将图像进行存储,C#读取处理过后的图像。
C#与python通信有好几种,为什么选择共享内存?
处理图像的速度需求是1秒钟处理5张以上,通过进程调用的方式无法达到速度的要求,共享内存可以。
优点是速度快,缺点是需要在运行的电脑上安装python环境。
好了,废话说完了,下面是代码示例
我使用的框架是Framework4.6.2,首先需要安装pythonnet NuGet包。框架依赖项显示需要.Net2.0及以上,实测Framework4.6.2也可以使用
实现源码:C#部分
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源码:
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都是正常的,再次运行耗时很短,约几毫秒左右。