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

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

【料理】 だしの素で簡単!そうめんチャンプルー

ここを参考に作りました。

cookpad.com

材料(2人分)

  • そうめん   2束
  • しめじ    1パック
  • 長ネギ    1本
  • ツナ     1缶
  • 醤油     大さじ1.5
  • ごま油    大さじ1
  • 塩      ひとつまみ
  • 顆粒だしの素 小さじ0.5

作り方

  1. 鍋に湯を沸かす
  2. 長ネギは1cmの斜め切りにする
  3. しめじの石づきを切り落とし、小房に分ける
  4. そうめんを半分に折って湯で沸かす
  5. そうめんを沸かしている間に醤油以下を混ぜておく
  6. そうめんが沸いたら流水でぬめりを取り水を張ったざるに入れておく
  7. フライパンにツナを油ごと入れて火をつける
  8. 長ネギを入れて炒める
  9. しめじを入れて炒める
  10. 混ぜた調味料を入れる
  11. 水気を切ったそうめんを入れ、調味料がなじんだら火を止める
  12. 味見をして物足りなければ塩コショウをふる

【Python】 Kerasでpix2pixを試す

先日U-Netによるセグメンテーションを試した(【Python】 KerasでU-Net構造ネットワークによるセグメンテーションをする - 旅行好きなソフトエンジニアの備忘録)のですが、ついでにDiscriminatorを作ればpix2pixを試せるということで、作ってみました。U-Netの時と同様、プログラムは下記を参考にしました。

github.com

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

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

まずは、以下をpix2pix.pyとして作成します。pix2pix.pyにはpix2pixの元となるGeneratorクラス、Discriminatorクラスとそれらを利用するPix2pixクラスを書きます。

# -*- coding: utf-8 -*-
import numpy as np
import cv2
from keras.layers import Input, LeakyReLU, BatchNormalization, Activation, Dropout, Flatten, Dense
from keras.layers.convolutional import Conv2D, ZeroPadding2D, Conv2DTranspose
from keras.layers.merge import concatenate
from keras.optimizers import Adam
from keras.models import Model

class Generator(object):
    def __init__(self, input_image_size, input_channel_count, output_channel_count, first_layer_filter_count,
                 concatenate_axis, image_order):
        self.CONV_FILTER_SIZE = 4
        self.CONV_STRIDE = 2
        self.CONV_PADDING = (1, 1)
        self.DECONV_FILTER_SIZE = 2
        self.DECONV_STRIDE = 2
        self.CONCATENATE_AXIS = concatenate_axis
        self.IMAGE_ORDER = image_order

        self.inputs = Input((input_image_size, input_image_size, input_channel_count))

        enc1 = ZeroPadding2D(self.CONV_PADDING)(self.inputs)
        enc1 = Conv2D(first_layer_filter_count, self.CONV_FILTER_SIZE, strides=self.CONV_STRIDE, data_format=self.IMAGE_ORDER)(enc1)

        filter_count = first_layer_filter_count*2
        enc2 = self._add_encoding_layer(filter_count, enc1)

        filter_count = first_layer_filter_count*4
        enc3 = self._add_encoding_layer(filter_count, enc2)

        filter_count = first_layer_filter_count*8
        enc4 = self._add_encoding_layer(filter_count, enc3)

        enc5 = self._add_encoding_layer(filter_count, enc4)

        enc6 = self._add_encoding_layer(filter_count, enc5)

        enc7 = self._add_encoding_layer(filter_count, enc6)

        enc8 = self._add_encoding_layer(filter_count, enc7)

        dec1 = self._add_decoding_layer(filter_count, True, enc8)
        dec1 = concatenate([dec1, enc7], axis=self.CONCATENATE_AXIS)

        dec2 = self._add_decoding_layer(filter_count, True, dec1)
        dec2 = concatenate([dec2, enc6], axis=self.CONCATENATE_AXIS)

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

        dec4 = self._add_decoding_layer(filter_count, False, dec3)
        dec4 = concatenate([dec4, enc4], axis=self.CONCATENATE_AXIS)

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

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

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

        dec8 = Activation(activation='relu')(dec7)
        dec8 = Conv2DTranspose(output_channel_count, self.DECONV_FILTER_SIZE, strides=self.DECONV_STRIDE, data_format=self.IMAGE_ORDER)(dec8)
        dec8 = Activation(activation='tanh')(dec8)

        self.sequence = dec8

    def _add_encoding_layer(self, filter_count, sequence):
        new_sequence = LeakyReLU(0.2)(sequence)
        new_sequence = ZeroPadding2D(self.CONV_PADDING, data_format=self.IMAGE_ORDER)(new_sequence)
        new_sequence = Conv2D(filter_count, self.CONV_FILTER_SIZE, strides=self.CONV_STRIDE, data_format=self.IMAGE_ORDER)(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', data_format=self.IMAGE_ORDER)(new_sequence)
        new_sequence = BatchNormalization()(new_sequence)
        if add_drop_layer:
            new_sequence = Dropout(0.5)(new_sequence)
        return new_sequence

    def get_sequence(self):
        return self.sequence


class Discriminator(object):
    def __init__(self, input_image_size, input_channel_count, output_channel_count, first_layer_filter_count,
                 concatenate_axis, image_order):
        CONCAENATE_AXIS = concatenate_axis
        CONV_FILTER_SIZE = 4
        CONV_STRIDE = 2
        IMAGE_ORDER = image_order

        self.inputs1 = Input((input_image_size, input_image_size, input_channel_count))
        self.inputs2 = Input((input_image_size, input_image_size, output_channel_count))
        inputs = concatenate(inputs=[self.inputs1, self.inputs2], axis=CONCAENATE_AXIS)

        sequence = Conv2D(first_layer_filter_count, CONV_FILTER_SIZE, strides=CONV_STRIDE, data_format=IMAGE_ORDER)(inputs)
        sequence = LeakyReLU(alpha=0.2)(sequence)

        sequence = Conv2D(first_layer_filter_count*2, CONV_FILTER_SIZE, strides=CONV_STRIDE, data_format=IMAGE_ORDER)(sequence)
        sequence = BatchNormalization()(sequence)
        sequence = LeakyReLU(alpha=0.2)(sequence)

        sequence = Conv2D(first_layer_filter_count*4, CONV_FILTER_SIZE, strides=CONV_STRIDE, data_format=IMAGE_ORDER)(sequence)
        sequence = BatchNormalization()(sequence)
        sequence = LeakyReLU(alpha=0.2)(sequence)

        sequence = Conv2D(first_layer_filter_count*8, CONV_FILTER_SIZE, data_format=IMAGE_ORDER)(sequence)
        sequence = BatchNormalization()(sequence)
        sequence = LeakyReLU(alpha=0.2)(sequence)

        sequence = Flatten()(sequence)
        sequence = Dense(2, activation='softmax')(sequence)

        self.sequence = sequence

    def get_sequence(self):
        return self.sequence


class Pix2pix(object):
    def __init__(self, input_channel_count, output_channel_count, first_layer_filter_count,
                 concatenate_axis, image_order):
        INPUT_IMAGE_SIZE = 256

        generator = Generator(INPUT_IMAGE_SIZE, input_channel_count, output_channel_count, first_layer_filter_count, concatenate_axis, image_order)
        generator_sequence = generator.get_sequence()

        descriminator = Discriminator(INPUT_IMAGE_SIZE, input_channel_count, output_channel_count, first_layer_filter_count, concatenate_axis, image_order)
        descriminator_sequence = descriminator.get_sequence()

        self.generator = Model(inputs=generator.inputs, outputs=[generator_sequence, generator.inputs], name='generator')
        self.discriminator = Model(inputs=[descriminator.inputs1, descriminator.inputs2], outputs=[descriminator_sequence], name='discriminator')
        pix2pix_sequence = self.discriminator(self.generator(generator.inputs))
        self.pix2pix = Model(inputs=generator.inputs, outputs=[generator_sequence, pix2pix_sequence])

    def train(self, learning_rate, X_output_positive_for_discriminator, X_input_positive_for_discriminator,
              X_input_negative_for_discriminator, X_inputs_for_generator, X_outputs_for_generator):
        # Prepare for discriminator training
        self.discriminator.trainable = True
        self.discriminator.compile(loss='binary_crossentropy', optimizer=Adam(lr=learning_rate))
        # Train discriminator with positive data
        Y_positive_for_discriminator = np.array([0, 1], np.float32)
        Y_positive_for_discriminator = Y_positive_for_discriminator[np.newaxis, :]
        loss_discriminator1 = self.discriminator.train_on_batch([X_output_positive_for_discriminator, X_input_positive_for_discriminator], Y_positive_for_discriminator)
        # Train discriminator with negative data
        X_output_negative_for_discriminator = self.generator.predict_on_batch(X_input_negative_for_discriminator)
        Y_negative_for_discriminator = np.array([1, 0], np.float32)
        Y_negative_for_discriminator = Y_negative_for_discriminator[np.newaxis, :]
        loss_discriminator2 = self.discriminator.train_on_batch([X_output_negative_for_discriminator[0], X_input_negative_for_discriminator], Y_negative_for_discriminator)
        loss_discriminator = (loss_discriminator1 + loss_discriminator2)/2.0


        # Prepare for generator training
        self.generator.trainable = True
        self.discriminator.trainable = False
        self.pix2pix.compile(loss=['mae', 'binary_crossentropy'], loss_weights=[100.0, 1], optimizer=Adam(lr=learning_rate))
        # Train generator
        Y1_for_generator = X_outputs_for_generator
        Y2_for_generator = np.array([[0, 1], [0, 1]], np.float32)
        Y_for_generator = [Y1_for_generator, Y2_for_generator]
        loss_generator = self.pix2pix.train_on_batch(X_inputs_for_generator, Y_for_generator)

        return loss_discriminator, np.average(loss_generator)

    def load_weights(self, file_path):
        self.pix2pix.load_weights(file_path)

    def save_weights(self, file_path, overwrite):
        self.pix2pix.save_weights(file_path, overwrite)

    def predict_on_batch(self, X):
        return self.generator.predict_on_batch(X)[0]


次にここからfacades.tar.gzをダウンロードして解凍します。解凍すると、train, val, testという3つのフォルダがあり、これらを利用して学習を行います。
次に以下をmain.pyとして作成します。ファイル"main.py", “pix2pix.py"とfacades.tar.gzを解凍した時に得られるフォルダ"train”, “val”, “test”, 予測結果を出力するためのフォルダ"prediction_pix2pix"は同じフォルダに置いてあるものとします。

# -*- coding: utf-8 -*-

import os
import numpy as np
from pix2pix import Pix2pix
import keras.backend as K

IMAGE_SIZE = 256
INPUT_CHANNEL_COUNT = 3
OUTPUT_CHANNEL_COUNT = 3
FIRST_LAYER_FILTER_COUNT = 64
CONCATENATE_AXIS = -1
IMAGE_ORDER = 'channels_last'
WEIGHT_FILE_NAME = 'pix2pix_weights.hdf5'

# Function to normalize values to -1 ~ 1
def normalize(image):
    image = image/127.5 - 1
    return image


# Function to denormalize values to 0 ~ 255
def denormalize(image):
    image = (image + 1)/2*255
    return image


def load_images(folder_path):
    import os, cv2
    import random

    image_files = os.listdir(folder_path)
    random.shuffle(image_files)
    inputs = np.zeros((len(image_files), IMAGE_SIZE, IMAGE_SIZE, 3), np.float32)
    outputs = 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 + '\\' + image_file)
        image = normalize(image)
        inputs[i] = image[:, IMAGE_SIZE:, :]
        outputs[i] = image[:, :IMAGE_SIZE, :]
    return inputs, outputs


def train_pix2pix():
    learning_rate = 0.0001
    epochs = 200

    for e in range(epochs):
        X_train_inputs, X_train_outputs = load_images('train')
        for i in range(X_train_inputs.shape[0]//4):
            network = Pix2pix(INPUT_CHANNEL_COUNT, OUTPUT_CHANNEL_COUNT, FIRST_LAYER_FILTER_COUNT, CONCATENATE_AXIS, IMAGE_ORDER)
            network.load_weights(WEIGHT_FILE_NAME)
            X_output_positive_for_descriminator = X_train_outputs[np.newaxis, 4*i, :, :]
            X_input_positive_for_descriminator = X_train_inputs[np.newaxis, 4*i, :, :]
            X_input_negative_for_descriminator = X_train_inputs[np.newaxis, 4*i + 1, :, :]
            X_inputs_for_generator = np.zeros((2, IMAGE_SIZE, IMAGE_SIZE, INPUT_CHANNEL_COUNT), dtype=np.float32)
            X_inputs_for_generator[0, :, :, :] = X_train_inputs[np.newaxis, 4*i + 2, :, :]
            X_inputs_for_generator[1, :, :, :] = X_train_inputs[np.newaxis, 4*i + 3, :, :]
            X_outputs_for_generator = np.zeros((2, IMAGE_SIZE, IMAGE_SIZE, INPUT_CHANNEL_COUNT), dtype=np.float32)
            X_outputs_for_generator[0, :, :, :] = X_train_outputs[np.newaxis, 4*i + 2, :, :]
            X_outputs_for_generator[1, :, :, :] = X_train_outputs[np.newaxis, 4*i + 3, :, :]
            loss_discriminator, loss_generator = network.train(learning_rate,
                                                               X_output_positive_for_descriminator,
                                                               X_input_positive_for_descriminator,
                                                               X_input_negative_for_descriminator,
                                                               X_inputs_for_generator,
                                                               X_outputs_for_generator)
            network.save_weights(WEIGHT_FILE_NAME, True)
            K.clear_session()
            print(str(e) + ':' + str(i) + ' finish')
            print()


def predict():
    import cv2

    X_test_inputs, X_test_outputs = load_images('test')

    pix2pix = Pix2pix(INPUT_CHANNEL_COUNT, OUTPUT_CHANNEL_COUNT, FIRST_LAYER_FILTER_COUNT, CONCATENATE_AXIS, IMAGE_ORDER)
    pix2pix.load_weights(WEIGHT_FILE_NAME)
    for i, X_test_input in enumerate(X_test_inputs):
        X_test_input = X_test_input[np.newaxis, :, :, :]
        Y_pred = pix2pix.predict_on_batch(X_test_input)
        prediction = denormalize(Y_pred[0])
        input = denormalize(X_test_input[0])
        output = denormalize(X_test_outputs[i])
        cv2.imwrite('prediction_pix2pix' + os.sep + 'input' + str(i) + '.jpg', input)
        cv2.imwrite('prediction_pix2pix' + os.sep + 'output' + str(i) + '.jpg', output)
        cv2.imwrite('prediction_pix2pix' + os.sep + 'prediction' + str(i) + '.jpg', prediction)


if __name__ == '__main__':
    train_pix2pix()
    predict()


学習を進めると徐々に画像が細部を再現してくるようになりました。

1エポック終了時

f:id:ni4muraano:20170816211329p:plain

5エポック終了時

f:id:ni4muraano:20170816211338p:plain

10エポック終了時

f:id:ni4muraano:20170816211600p:plain

35エポック終了時

f:id:ni4muraano:20170819150641p:plain

まだまだ細部を再現出来ていないのですが、35/200エポックの学習が済んだ時点で既に3日経過しており、パソコンをそろそろ普通に使いたいのでプログラムを止めました。やはりCPUのみでディープラーニングを試すのは厳しいなと実感してます。あと、学習を続ける中で以下の懸念点があり、このまま学習を続けても上手くいくのか分からなくなったこともプログラムを止めた一つの理由です。

  • プログラムは正しい?
  • Generatorが8~20のロス値を取り続け収束しない
  • Discriminatorが0~8のロス値を取り続け収束しない
  • loss_weights=[100.0, 1.0]ということは、Discriminatorの判定結果はあまり重要でない?⇒実はDiscriminator無くても出来てしまう?

今後は寝てる時や会社にいる間に少しずつ走らせて、合計100エポック程度走らせるとどうなるかを確認したいと思います。

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

ここを見て作りました。

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)。

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

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

  • 初期の頃は"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

【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のモデル生成部の不具合を修正)。

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