【C#】【Python】Python実行結果をリアルタイムにC#に送る


はじめに

Pythonスクリプトの実行結果(数字)をリアルタイムにWPFで作ったWindowに表示させることが目的です。

f:id:touch-sp:20211012225050p:plain:w500
ボトル1本
f:id:touch-sp:20211012225119p:plain:w500
ボトル2本

Webカメラからの映像をPython内のOpenCVを使って右側に表示しています。Python内でボトルの数を数え、その結果をC#にリアルタイムに送信しています。


左側のWindowはWPFで作ったものでボトルの本数が表示されています。


静止画で分かりにくいですがWebカメラに映りこむボトルの本数が変わればWPFのWindowの表示がリアルタイムに変化します。


Pythonでやったことは過去の2記事を参照して下さい。

環境

Windows 11 
Core i7-7700K + GTX 1080

Visual Studio Community 2019
.NET 5.0

Python 3.7.9
autocfg==0.0.8
certifi==2021.5.30
chardet==3.0.4
colorama==0.4.4
cycler==0.10.0
gluoncv==0.10.4.post4
graphviz==0.8.4
idna==2.6
kiwisolver==1.3.2
matplotlib==3.4.3
mxnet-cu102==1.7.0
numpy==1.21.2
opencv-python==4.5.3.56
pandas==1.3.3
Pillow==8.3.2
portalocker==2.3.2
pyparsing==2.4.7
python-dateutil==2.8.2
pytz==2021.3
pywin32==301
PyYAML==5.4.1
requests==2.18.4
scipy==1.7.1
six==1.16.0
tqdm==4.62.3
urllib3==1.22
yacs==0.1.8

Python仮想環境にインストールしたのは「mxnet」と「gluoncv」のみです。二つをインストールすると他はついてきます。
インストール方法はこちらを参照して下さい。


GPUがないと動作がカクカクになります。もっと軽い物体検出モデルを選択すれば問題ないと思います。

Pythonスクリプト

特別な書き換えは必要ありません。カウント結果をC#に送るのは以下の1行です。

print(count)

全体のスクリプトは以下のようになっています。

import cv2
import mxnet as mx
from gluoncv import model_zoo, data, utils

ctx = mx.gpu() if mx.context.num_gpus() >0 else mx.cpu()

cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)

net = model_zoo.get_model('faster_rcnn_fpn_resnet101_v1d_coco', pretrained=True, root='./models', ctx=ctx)
net.reset_class(['bottle'], reuse_weights=['bottle'])

while True:
    ret, frame = cap.read()
    
    frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    mx_image = mx.nd.array(frame_rgb)

    x, img = data.transforms.presets.rcnn.transform_test(mx_image, short=480)
    class_IDs, scores, bounding_boxs = net(x.as_in_context(ctx))
    count = int(mx.nd.sum(scores[0]>0.8).asscalar())    

    print(count)

    display_img = utils.viz.cv_plot_bbox(img, bounding_boxs[0], scores[0], class_IDs[0], class_names=net.classes)
    utils.viz.cv_plot_image(display_img)

    if cv2.waitKey(1) & 0xFF == 27:
        break

cap.release()
cv2.destroyAllWindows()

C#コード

今回もXAMLは記述していません。「WindowSettings.cs」内にGUI部分を記述しています。

MainWindow.xaml.cs

今回初めて「Dispatcher」について勉強しました。

C#側からPythonを呼び出すときになぜか「-u」オプションをつけることが必要です。
この部分でだいぶはまりました。

using System;
using System.Diagnostics;
using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        Process myProcess = new Process();

        public MainWindow()
        {
            InitializeComponent();
            WindowSettings();
        }
        private void PushStartButton(object sender, RoutedEventArgs e)
        {
            if (String.IsNullOrEmpty(selectedPythonPath)) return;

            PythonStart.IsEnabled = false;
            PythonStop.IsEnabled = true;

            string myPythonApp = "-u cv.py";

            myProcess = new Process
            {
                StartInfo = new ProcessStartInfo(selectedPythonPath)
                {
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    CreateNoWindow = true,
                    Arguments = myPythonApp
                }
            };
            myProcess.OutputDataReceived += DataReceive;
            myProcess.Start();
            myProcess.BeginOutputReadLine();
        }
        private void PushStopButton(object sender, RoutedEventArgs e)
        {
            myProcess.Kill();
            PythonStop.IsEnabled = false;
            PythonStart.IsEnabled = true;
        }
        private void DataReceive(object sender, DataReceivedEventArgs e)
        {
            if(e != null && e.Data !=null && e.Data.Length > 0)
            {
                Dispatcher.Invoke((Action)(() =>
                {
                    resultLabel.Content = e.Data;
                }));
            }
        }
    }
}

WindowSettings.cs

using Microsoft.Win32;
using System.Windows;
using System.Windows.Controls;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        string selectedPythonPath;

        Label resultLabel = new Label() { Content = "0" };

        Button PythonStart = new()
        {
            Content = new Viewbox() { Child = new Label() { Content = "Start Python" } },
            Margin = new Thickness(10)
        };
        Button PythonStop = new() 
        {
            Content = new Viewbox() { Child = new Label() { Content = "Stop Python" } },
            Margin = new Thickness(10),
            IsEnabled = false 
        };
        Button selectPythonButton = new Button()
        {
            Content = new Viewbox() { Child = new Label() { Content = "Select Python Interpreter" } },
            Margin = new Thickness(5)
        };
        Label selectedPythonLabel = new Label()
        {
            Content = "",
            VerticalAlignment = VerticalAlignment.Center,
            HorizontalAlignment = HorizontalAlignment.Left
        };

        private void WindowSettings()
        {
            selectPythonButton.Click += PushselectPythonButton;
            PythonStart.Click += PushStartButton;
            PythonStop.Click += PushStopButton;

            Grid mainGrid = Myfunc.NewGrid("1,8,1", "");
            Content = mainGrid;

            Grid upperGrid = Myfunc.NewGrid("", "3,7");
            upperGrid.AddChild(selectPythonButton, 0, 0, 1, 1);
            upperGrid.AddChild(selectedPythonLabel, 0, 1, 1, 1);

            Grid lowerGrid = Myfunc.NewGrid("", "1,1");
            Viewbox vb = new Viewbox()
            {
                Child = resultLabel,
                Margin = new Thickness(5)
            };
            lowerGrid.AddChild(vb, 0, 0, 1, 1);

            mainGrid.AddChild(upperGrid, 0, 0, 1, 1);
            mainGrid.AddChild(lowerGrid, 1, 0, 1, 1);

            Grid buttonGrid = Myfunc.NewGrid("1,1", "");
            lowerGrid.AddChild(buttonGrid, 0, 1, 1, 1);

            buttonGrid.AddChild(PythonStart, 0, 0, 1, 1);
            buttonGrid.AddChild(PythonStop, 1, 0, 1, 1);

        }
        private void PushselectPythonButton(object sender, RoutedEventArgs e)
        {
            var dialog = new OpenFileDialog();
            dialog.Filter = "Python実行ファイル (*.exe)|*.exe";
            if (dialog.ShowDialog() == true)
            {
                selectedPythonPath = dialog.FileName;
                selectedPythonLabel.Content = selectedPythonPath;
            }
        }
    }
}

MyFunc.cs

Gridを作成してChildrenを追加するときに役に立つメソッドです。

using System.Windows;
using System.Windows.Controls;

static class Myfunc
{
    public static Grid NewGrid(string row, string col) //引数はカンマ区切りで
    {
        Grid grid = new Grid();

        string[] grid_row = row.Split(",");
        string[] grid_col = col.Split(",");

        if (grid_row.Length > 1)
        {
            foreach (string each in grid_row)
            {
                if (int.TryParse(each, out int each_num))
                {
                    grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(each_num, GridUnitType.Star) });
                }
                else
                {
                    grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
                }
            }
        }
        if (grid_col.Length > 1)
        {
            foreach (string each in grid_col)
            {
                if (int.TryParse(each, out int each_num))
                {
                    grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(each_num, GridUnitType.Star) });
                }
                else
                {
                    grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
                }
            }
        }
        return grid;
    }
    public static Grid AddChild(this Grid grid, UIElement element, int row, int col, int row_span, int col_span)
    {
        Grid.SetRow(element, row);
        Grid.SetRowSpan(element, row_span);
        Grid.SetColumn(element, col);
        Grid.SetColumnSpan(element, col_span);
        grid.Children.Add(element);

        return grid;
    }
}

おわりに

間違いや改善点があればコメント頂けましたら幸いです。