旅行好きなソフトエンジニアの備忘録

プログラミングや技術関連のメモを始めました

【料理】 ズッキーニのシャキッとマヨ炒め

ここを見て作りました。

cookpad.com

材料 (2人分)

  • ズッキーニ 1本
  • エノキ 1/3袋
  • ベーコン 2枚
  • マヨネーズ大匙 1と1/2
  • しょうゆ小匙 1弱
  • 塩少々

作り方

  1. ズッキーニを厚さ8ミリくらいの輪切りにする。 エノキは根元を切って、小房に分ける。 ベーコンは一口大に切る。
  2. フライパンにマヨネーズ大さじ1と1/2を入れて、火をつける。 マヨネーズが溶けたら、ズッキーニを入れる。

  3. ズッキーニをひっくり返してから、塩少々をふる。 ベーコンとエノキを入れる。

  4. エノキに火が通ったら、しょうゆを回しかける。 サッと混ぜたら完成!

【ディープラーニング】 セマンティックセグメンテーション手法のまとめ

ディープラーニングを利用したセマンティックセグメンテーションについてまとめてあるページを見つけたのでメモします(A 2017 Guide to Semantic Segmentation with Deep Learning)。

2017年12月10日追記 上記リンクを日本語訳した記事があったため、下記リンクを参照した方が良いです。 postd.cc

セマンティックセグメンテーションとは?

どんなアプローチがあるの?

  • 初期の頃は"patch classification"が用いられており、全結合層があるため入力画像のサイズは固定だった

  • FCNが発表されてからはFCNがメジャーになった。FCNにより任意の画像サイズを扱うことが出来るようになり、"patch classification"よりも非常に高速という特徴がある

  • CNNをセグメンテーションに用いることの欠点としてプーリング層の存在がある。プーリング層があることでピクセルの細かい位置情報に捉われることがなくなるが、セグメンテーションは位置情報が重要

  • 位置情報の問題に対処するために2つのアーキテクチャがある。 一つはエンコーダーデコーダー型でU-Netが有名。もう一つはdilated/atrous convolutions

  • conditional random fieldによる後処理と併用すると、スコアが1~2%上昇する

読むべき論文

  1. [1411.4038] Fully Convolutional Networks for Semantic Segmentation

  2. [1511.00561] SegNet: A Deep Convolutional Encoder-Decoder Architecture for Image Segmentation

  3. [1511.07122] Multi-Scale Context Aggregation by Dilated Convolutions

  4. [1412.7062] Semantic Image Segmentation with Deep Convolutional Nets and Fully Connected CRFs

  5. [1606.00915] DeepLab: Semantic Image Segmentation with Deep Convolutional Nets, Atrous Convolution, and Fully Connected CRFs

  6. [1611.06612] RefineNet: Multi-Path Refinement Networks for High-Resolution Semantic Segmentation

  7. [1612.01105] Pyramid Scene Parsing Network

  8. [1703.02719] Large Kernel Matters -- Improve Semantic Segmentation by Global Convolutional Network

  9. [1706.05587] Rethinking Atrous Convolution for Semantic Image Segmentation

読むべき論文追記(精度は劣るが実行スピードが速い)

  1. ENet
    [1606.02147] ENet: A Deep Neural Network Architecture for Real-Time Semantic Segmentation

  2. ICNet
    [1704.08545] ICNet for Real-Time Semantic Segmentation on High-Resolution Images

  3. ERFNet
    http://www.robesafe.uah.es/personal/eduardo.romera/pdfs/Romera17iv.pdf

【Python】 KerasでU-Net構造ネットワークによるセグメンテーションをする

ここ(Daimler Pedestrian Segmentation Benchmark)から取得できるデータセットを使って、写真から人を抽出するセグメンテーション問題を解いてみます。U-Netはここ( U-Net: Convolutional Networks for Biomedical Image Segmentation )で初めて発表された構造と思いますが、セグメンテーション問題にMax Poolingを使うのは良くないといった話があったり、Batch Normalization等も使いたいということで、pix2pixのGeneratorとして利用されているU-Net構造のネットワークを利用します。

github.com


実行環境は以下になります。

  • Windows10 64bit
  • Python3.5.2
  • Keras2.0.4
  • BackendはTensorflow CPU版

まずはU-Netモデルを作るクラスです(unet.py)。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from keras.models import Model
from keras.layers import Input
from keras.layers.convolutional import Conv2D, ZeroPadding2D, Conv2DTranspose
from keras.layers.merge import concatenate
from keras.layers import LeakyReLU, BatchNormalization, Activation, Dropout

class UNet(object):
    def __init__(self, input_channel_count, output_channel_count, first_layer_filter_count):
        self.INPUT_IMAGE_SIZE = 256
        self.CONCATENATE_AXIS = -1
        self.CONV_FILTER_SIZE = 4
        self.CONV_STRIDE = 2
        self.CONV_PADDING = (1, 1)
        self.DECONV_FILTER_SIZE = 2
        self.DECONV_STRIDE = 2

        # (256 x 256 x input_channel_count)
        inputs = Input((self.INPUT_IMAGE_SIZE, self.INPUT_IMAGE_SIZE, input_channel_count))

        # エンコーダーの作成
        # (128 x 128 x N)
        enc1 = ZeroPadding2D(self.CONV_PADDING)(inputs)
        enc1 = Conv2D(first_layer_filter_count, self.CONV_FILTER_SIZE, strides=self.CONV_STRIDE)(enc1)

        # (64 x 64 x 2N)
        filter_count = first_layer_filter_count*2
        enc2 = self._add_encoding_layer(filter_count, enc1)

        # (32 x 32 x 4N)
        filter_count = first_layer_filter_count*4
        enc3 = self._add_encoding_layer(filter_count, enc2)

        # (16 x 16 x 8N)
        filter_count = first_layer_filter_count*8
        enc4 = self._add_encoding_layer(filter_count, enc3)

        # (8 x 8 x 8N)
        enc5 = self._add_encoding_layer(filter_count, enc4)

        # (4 x 4 x 8N)
        enc6 = self._add_encoding_layer(filter_count, enc5)

        # (2 x 2 x 8N)
        enc7 = self._add_encoding_layer(filter_count, enc6)

        # (1 x 1 x 8N)
        enc8 = self._add_encoding_layer(filter_count, enc7)

        # デコーダーの作成
        # (2 x 2 x 8N)
        dec1 = self._add_decoding_layer(filter_count, True, enc8)
        dec1 = concatenate([dec1, enc7], axis=self.CONCATENATE_AXIS)

        # (4 x 4 x 8N)
        dec2 = self._add_decoding_layer(filter_count, True, dec1)
        dec2 = concatenate([dec2, enc6], axis=self.CONCATENATE_AXIS)

        # (8 x 8 x 8N)
        dec3 = self._add_decoding_layer(filter_count, True, dec2)
        dec3 = concatenate([dec3, enc5], axis=self.CONCATENATE_AXIS)

        # (16 x 16 x 8N)
        dec4 = self._add_decoding_layer(filter_count, False, dec3)
        dec4 = concatenate([dec4, enc4], axis=self.CONCATENATE_AXIS)

        # (32 x 32 x 4N)
        filter_count = first_layer_filter_count*4
        dec5 = self._add_decoding_layer(filter_count, False, dec4)
        dec5 = concatenate([dec5, enc3], axis=self.CONCATENATE_AXIS)

        # (64 x 64 x 2N)
        filter_count = first_layer_filter_count*2
        dec6 = self._add_decoding_layer(filter_count, False, dec5)
        dec6 = concatenate([dec6, enc2], axis=self.CONCATENATE_AXIS)

        # (128 x 128 x N)
        filter_count = first_layer_filter_count
        dec7 = self._add_decoding_layer(filter_count, False, dec6)
        dec7 = concatenate([dec7, enc1], axis=self.CONCATENATE_AXIS)

        # (256 x 256 x output_channel_count)
        dec8 = Activation(activation='relu')(dec7)
        dec8 = Conv2DTranspose(output_channel_count, self.DECONV_FILTER_SIZE, strides=self.DECONV_STRIDE)(dec8)
        dec8 = Activation(activation='sigmoid')(dec8)

        self.UNET = Model(input=inputs, output=dec8)

    def _add_encoding_layer(self, filter_count, sequence):
        new_sequence = LeakyReLU(0.2)(sequence)
        new_sequence = ZeroPadding2D(self.CONV_PADDING)(new_sequence)
        new_sequence = Conv2D(filter_count, self.CONV_FILTER_SIZE, strides=self.CONV_STRIDE)(new_sequence)
        new_sequence = BatchNormalization()(new_sequence)
        return new_sequence

    def _add_decoding_layer(self, filter_count, add_drop_layer, sequence):
        new_sequence = Activation(activation='relu')(sequence)
        new_sequence = Conv2DTranspose(filter_count, self.DECONV_FILTER_SIZE, strides=self.DECONV_STRIDE,
                                       kernel_initializer='he_uniform')(new_sequence)
        new_sequence = BatchNormalization()(new_sequence)
        if add_drop_layer:
            new_sequence = Dropout(0.5)(new_sequence)
        return new_sequence

    def get_model(self):
        return self.UNET


次に前処理関連の関数です(main.py)。

IMAGE_SIZE = 256

# 値を-1から1に正規化する関数
def normalize_x(image):
    image = image/127.5 - 1
    return image


# 値を0から1に正規化する関数
def normalize_y(image):
    image = image/255
    return image


# 値を0から255に戻す関数
def denormalize_y(image):
    image = image*255
    return image


# インプット画像を読み込む関数
def load_X(folder_path):
    import os, cv2

    image_files = os.listdir(folder_path)
    image_files.sort()
    images = np.zeros((len(image_files), IMAGE_SIZE, IMAGE_SIZE, 3), np.float32)
    for i, image_file in enumerate(image_files):
        image = cv2.imread(folder_path + os.sep + image_file)
        image = cv2.resize(image, (IMAGE_SIZE, IMAGE_SIZE))
        images[i] = normalize_x(image)
    return images, image_files


# ラベル画像を読み込む関数
def load_Y(folder_path):
    import os, cv2

    image_files = os.listdir(folder_path)
    image_files.sort()
    images = np.zeros((len(image_files), IMAGE_SIZE, IMAGE_SIZE, 1), np.float32)
    for i, image_file in enumerate(image_files):
        image = cv2.imread(folder_path + os.sep + image_file, cv2.IMREAD_GRAYSCALE)
        image = cv2.resize(image, (IMAGE_SIZE, IMAGE_SIZE))
        image = image[:, :, np.newaxis]
        images[i] = normalize_y(image)
    return images


最後にこれらを利用するメイン関数です(main.py)。これを動作させるためには以下のファイル/フォルダ構造を想定しています。
- main.py
- unet.py
- trainingData(トレーニング用データを格納するフォルダ)
- testData(テスト用データを格納するフォルダ)
更にtrainingDataフォルダ、testDataフォルダには最初に紹介したデータセットにあるleft_imagesフォルダとleft_groundTruthフォルダがそれぞれ置かれているものとします。

  • 2017/08/10 関数predictのモデル生成部の不具合を修正
  • 2017/11/08 import osを書き忘れている不具合を修正
import os
import numpy as np
from keras.optimizers import Adam
import keras.backend as K
from keras.callbacks import ModelCheckpoint, EarlyStopping
from unet import UNet

# ダイス係数を計算する関数
def dice_coef(y_true, y_pred):
    y_true = K.flatten(y_true)
    y_pred = K.flatten(y_pred)
    intersection = K.sum(y_true * y_pred)
    return 2.0 * intersection / (K.sum(y_true) + K.sum(y_pred) + 1)


# ロス関数
def dice_coef_loss(y_true, y_pred):
    return 1.0 - dice_coef(y_true, y_pred)


# U-Netのトレーニングを実行する関数
def train_unet():
    # trainingDataフォルダ配下にleft_imagesフォルダを置いている
    X_train, file_names = load_X('trainingData' + os.sep + 'left_images')
    # trainingDataフォルダ配下にleft_groundTruthフォルダを置いている
    Y_train = load_Y('trainingData' + os.sep + 'left_groundTruth')

    # 入力はBGR3チャンネル
    input_channel_count = 3
    # 出力はグレースケール1チャンネル
    output_channel_count = 1
    # 一番初めのConvolutionフィルタ枚数は64
    first_layer_filter_count = 64
    # U-Netの生成
    network = UNet(input_channel_count, output_channel_count, first_layer_filter_count)
    model = network.get_model()
    model.compile(loss=dice_coef_loss, optimizer=Adam(), metrics=[dice_coef])

    BATCH_SIZE = 12
    # 20エポック回せば十分
    NUM_EPOCH = 20
    history = model.fit(X_train, Y_train, batch_size=BATCH_SIZE, epochs=NUM_EPOCH, verbose=1)
    model.save_weights('unet_weights.hdf5')


# 学習後のU-Netによる予測を行う関数
def predict():
    import cv2

    # testDataフォルダ配下にleft_imagesフォルダを置いている
    X_test, file_names = load_X('testData' + os.sep + 'left_images')

    input_channel_count = 3
    output_channel_count = 1
    first_layer_filter_count = 64
    network = UNet(input_channel_count, output_channel_count, first_layer_filter_count)
    model = network.get_model()
    model.load_weights('unet_weights.hdf5')
    BATCH_SIZE = 12
    Y_pred = model.predict(X_test, BATCH_SIZE)

    for i, y in enumerate(Y_pred):
        # testDataフォルダ配下にleft_imagesフォルダを置いている
        img = cv2.imread('testData' + os.sep + 'left_images' + os.sep + file_names[i])
        y = cv2.resize(y, (img.shape[1], img.shape[0]))
        cv2.imwrite('prediction' + str(i) + '.png', denormalize_y(y))


if __name__ == '__main__':
    train_unet()
    predict()
上手く抽出出来ている例(元画像/正解画像/予測画像)

f:id:ni4muraano:20170805002131p:plain:w100f:id:ni4muraano:20170805002139p:plain:w100f:id:ni4muraano:20170805002145p:plain:w100
f:id:ni4muraano:20170805002242p:plain:w100f:id:ni4muraano:20170805002252p:plain:w100f:id:ni4muraano:20170805002300p:plain:w100
f:id:ni4muraano:20170805002316p:plain:w100f:id:ni4muraano:20170805002323p:plain:w100f:id:ni4muraano:20170805002330p:plain:w100

上手くいかなかった例(元画像/正解画像/予測画像)

f:id:ni4muraano:20170805002518p:plain:w100f:id:ni4muraano:20170805002524p:plain:w100f:id:ni4muraano:20170805002527p:plain:w100
f:id:ni4muraano:20170805002541p:plain:w100f:id:ni4muraano:20170805002545p:plain:w100f:id:ni4muraano:20170805002547p:plain:w100
f:id:ni4muraano:20170805002603p:plain:w100f:id:ni4muraano:20170805002605p:plain:w100f:id:ni4muraano:20170805002609p:plain:w100

上手くいっていない例を見ると、学習データに類似パターンが無さそうなケースでした。学習していないことを予測できないのは仕方無いので、学習用データに十分なバリエーションを持たせて学習させることが重要と思いました。

【WPF】 簡易動画プレーヤーを作成する

今回は動画のプレーヤーを作ってみます(簡易的なものですが)。調べてみるとWPFにはMediaElementというものがあって、これを使ってみると良さそうです。
まずはxamlが以下のようになります。

<Window x:Class="CSMediaPlayer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CSMediaPlayer"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="6*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
        <MediaElement Name="MediaElementMovie" Grid.Row="0" Stretch="Fill" LoadedBehavior="Manual" UnloadedBehavior="Manual"/>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="4*"/>
            </Grid.ColumnDefinitions>
            <Button Name="ButtonPlay" Grid.Column="0" Content="▶" Click="ButtonPlay_Click"/>
            <Button Name="ButtonPause" Grid.Column="1" Content="||" Click="ButtonPause_Click"/>
            <Button Name="ButtonStop" Grid.Column="2" Content="■" Click="ButtonStop_Click"/>
            <Grid Grid.Column="3">
                <Slider Name="SliderMoviePosition" VerticalAlignment="Center" Minimum="0" Maximum="100" ValueChanged="SliderMoviePosition_ValueChanged"/>
            </Grid>
        </Grid>
    </Grid>
</Window>

上記のXAMLでこんな感じの見た目になります。あとはボタンやスライダーにイベントを割り振っていきます。
f:id:ni4muraano:20170730170109p:plain

using System;
using System.Windows;
using System.Windows.Threading;

namespace CSMediaPlayer
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        // 動画の相対パス
        private readonly string _videoPath = "video.avi";
        // スライダーを動かすためのタイマー
        private DispatcherTimer _timer = new DispatcherTimer();
        // タイマー起動間隔(1秒)
        private const int _timerInterval = 1;
        // 動画経過時間
        private int _elapsedSec = 0;
        // スライダーをユーザーが動かしたかどうか判別するフラグ
        private bool _sliderValueChangedByProgram = false;

        public MainWindow()
        {
            InitializeComponent();

            _timer.Interval = new TimeSpan(0, 0, _timerInterval);
            _timer.Tick += dispatcherTimer_Tick;
        }

        private void dispatcherTimer_Tick(object sender, EventArgs e)
        {
            // 動画経過時間に合わせてスライダーを動かす
            _elapsedSec += _timerInterval;
            double totalSec = MediaElementMovie.NaturalDuration.TimeSpan.TotalSeconds;
            SliderMoviePosition.Value = _elapsedSec / totalSec * SliderMoviePosition.Maximum;
            _sliderValueChangedByProgram = true;
        }

        private void ButtonPlay_Click(object sender, RoutedEventArgs e)
        {
            // 動画を再生
            _timer.Start();
            MediaElementMovie.Source = new Uri(_videoPath, UriKind.Relative);
            MediaElementMovie.Play();
        }

        private void ButtonPause_Click(object sender, RoutedEventArgs e)
        {
            // 動画を一次停止
            _timer.Stop();
            MediaElementMovie.Pause();
        }

        private void ButtonStop_Click(object sender, RoutedEventArgs e)
        {
            // 動画を停止
            _timer.Stop();
            _elapsedSec = 0;
            SliderMoviePosition.Value = 0;
            MediaElementMovie.Stop();
            MediaElementMovie.Source = null;
        }

        private void SliderMoviePosition_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (!_sliderValueChangedByProgram)
            {
                // スライダーを動かした位置に合わせて動画の再生箇所を更新する
                double totalSec = MediaElementMovie.NaturalDuration.TimeSpan.TotalSeconds;
                double sliderValue = SliderMoviePosition.Value;
                int targetSec = (int)(sliderValue * totalSec / SliderMoviePosition.Maximum);
                _elapsedSec = targetSec;
                TimeSpan ts = new TimeSpan(0, 0, 0, targetSec);
                MediaElementMovie.Position = ts;
            }
            _sliderValueChangedByProgram = false;
        }
    }
}


参考にしたサイトはここです(方法 : MediaElement (再生、一時停止、停止、ボリューム、および速度) を制御する )。サイトでは様々なイベントを定義しているので、きちんと作るのであれば上記コードは改善しないといけなさそうです。。。

【WPF】 スクリーンショットを撮り続けて動画に保存する

タイトルにあるようにスクリーンショットを撮り続けて動画に保存する方法のメモをします。
まずはスクリーンショットを撮る部分ですが、スクリーンショットをそのまま動画にすると容量がすごいことになるので、スクリーンショットを撮る⇒縮小するまでを作成します。

// Bitmapクラスを使うため参照の追加が必要
using System.Drawing;

private Bitmap GetScreenShot(int width, int height)
{
    var resizedBmp = new Bitmap(width, height);
    using (var bmp = new Bitmap((int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight))
    using (Graphics g = Graphics.FromImage(bmp))
    using (Graphics resizedG = Graphics.FromImage(resizedBmp))
    {
        // スクリーンショットを撮る
        g.CopyFromScreen(new System.Drawing.Point(0, 0), new System.Drawing.Point(0, 0), bmp.Size);

        // 動画サイズを減らすためリサイズする
        resizedG.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Bilinear;
        resizedG.DrawImage(bmp, 0, 0, width, height);
    }

    return resizedBmp;
}


一定時間ごとに上記メソッドを呼び出す必要があるので、DispatcherTimerクラスを利用します。動画保存はAccord.Video.VFWのAVIWriterクラスを利用します。Accord.Video.VFWはNuGETを使ってインストールします。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;

using Accord.Video.VFW;
using System.Drawing;
using System.Windows.Threading;

namespace CSMovie
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly DispatcherTimer _timer = new DispatcherTimer();
        private readonly AVIWriter _aviWriter = new AVIWriter();
        // フレームレート
        private readonly int _frameRate = 15;
        // 動画の横サイズ
        private readonly int _videoWidth = 640;
        // 動画の縦サイズ
        private readonly int _videoHeight = 480;
        private readonly List<Task> _tasks = new List<Task>();

        public MainWindow()
        {
            InitializeComponent();

            InitializeDispatcherTimer();

            InitializeVideo();
        }

        private void InitializeDispatcherTimer()
        {
            // フレームレートに合わせて起動時間を設定する
            int interval_msec = 1000 / _frameRate;
            _timer.Interval = new TimeSpan(0, 0, 0, 0, interval_msec);
            _timer.Tick += dispatcherTimer_Tick;
        }

        private void dispatcherTimer_Tick(object sender, EventArgs e)
        {
            // 時間がかかる処理なのでバックグラウンドで実行する
            _tasks.Add(
                Task.Run(() =>
                {
                    // スクリーンショットを撮る
                    using (Bitmap bmp = GetScreenShot(_videoWidth, _videoHeight))
                    {
                        // フレームに追加する
                        _aviWriter.AddFrame(bmp);
                    }
                }));
        }

        private void InitializeVideo()
        {
            _aviWriter.FrameRate = _frameRate;
            _aviWriter.Open("video.avi", _videoWidth, _videoHeight);
        }

        private void Window_ContentRendered(object sender, EventArgs e)
        {
            _timer.Start();
        }

        private Bitmap GetScreenShot(int width, int height)
        {
            var resizedBmp = new Bitmap(width, height);
            using (var bmp = new Bitmap((int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight))
            using (Graphics g = Graphics.FromImage(bmp))
            using (Graphics resizedG = Graphics.FromImage(resizedBmp))
            {
                // スクリーンショットを撮る
                g.CopyFromScreen(new System.Drawing.Point(0, 0), new System.Drawing.Point(0, 0), bmp.Size);

                // 動画サイズを減らすためリサイズする
                resizedG.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Bilinear;
                resizedG.DrawImage(bmp, 0, 0, width, height);
            }

            return resizedBmp;
        }

        private void Window_Closed(object sender, EventArgs e)
        {
            // タイマーを止める
            _timer.Stop();
            // 動作中のタスクがあれば待つ
            foreach (var task in _tasks)
            {
                task.Wait();
            }
            if (_aviWriter != null)
            {
                _aviWriter.Close();
                _aviWriter.Dispose();
            }
        }
    }
}


ちなみにスクリーンショットを撮る部分はここを参考にしました。

fieldlight.blog126.fc2.com

【Python】 PythonでC#のFileSystemWatcher相当のクラスを作成する

C#側がファイルを作成し、それをトリガとしてpythonが起動するようにしたいと思っています。そのため、C#のFileSystemWatcherに相当するようなクラスが欲しいと思ったのですが、以下のサイトに書かれていました。 qiita.com

import time
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler

class MyFileEventHandler(PatternMatchingEventHandler):
    def __init__(self, patterns):
        super(MyHandler, self).__init__(patterns=patterns)

    def on_moved(self, event):
        # ファイルが移動した時に実行されるメソッド
        print('file moved')

    def on_created(self, event):
        # ファイルが作成された時に実行されるメソッド
        print('file created')

    def on_deleted(self, event):
        # ファイルが削除された時に実行されるメソッド
        print('file deleted')

    def on_modified(self, event):
        # ファイルが更新された時に実行されるメソッド
        print('file modified')

# MyFileEventHandlerの使い方例
if __name__ == '__main__':
    # イベントを発生させるファイルパターン
    file_pattern_to_watch = '*.txt'
    # MyFileEventHandlerを生成して
    event_handler = MyFileEventHandler(file_pattern_to_watch)
    # Observerに登録する
    file_system_watcher = Observer()
    file_system_watcher.schedule(event_handler, directory_to_watch, recursive=False)
    # 監視を開始
    file_system_watcher.start()
    # 指定時間待つ
    time_out_sec = 30
    file_system_watcher.join(time_out_sec)
    # タイムアウトが起きたかどうか調べる
    if file_system_watcher.isAlive():
        # メモ:実際にタイムアウトが起きたらkillするしかない?
        print('timeout happened')