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

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

【OpenCV】 画像に楕円を書き込む

ellipseというメソッドを利用すると画像に楕円を書き込めますよ、というメモです。

// 画像を読み込む
Mat source;
imread("lenna.jpg").copyTo(source);
if (source.empty())
{
    throw runtime_error("Failed to open image");
}

Mat destination;
source.copyTo(destination);
RotatedRect rect = RotatedRect(Point2f(40.0, 40.0), Size2f(60.0, 20.0), 20.0);
Scalar color = Scalar(0, 0, 0);
int thickness = 2;
// 楕円を書き込む
ellipse(destination, rect, color, thickness);

f:id:ni4muraano:20170115110821j:plain:w200 f:id:ni4muraano:20170420211741j:plain:w200

【WPF】 時間のかかる処理を実行する時にアニメーションを表示する

アプリケーションで時間のかかる処理を実行している時に「今処理を実行中ですよ」とユーザーに知らせたい時、実行中に表示するアニメーションがあると便利です。幸いアニメーション自体は既に作成してくれている人がいるので、それを拝借します。

www.trillian.com.au

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    >
    <Color x:Key="FilledColor" A="255" B="155" R="155" G="155"/>
    <Color x:Key="UnfilledColor" A="0" B="155" R="155" G="155"/>

    <Style x:Key="BusyAnimationStyle" TargetType="Control">
        <Setter Property="Background" Value="#7F000000"/>

        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Control">
                    <ControlTemplate.Resources>
                        <Storyboard x:Key="Animation0" BeginTime="00:00:00.0" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames 
                                Storyboard.TargetName="ellipse0" 
                                Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                >
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>

                        <Storyboard x:Key="Animation1" BeginTime="00:00:00.2" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames 
                                Storyboard.TargetName="ellipse1" 
                                Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                >
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>

                        <Storyboard x:Key="Animation2" BeginTime="00:00:00.4" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames 
                                Storyboard.TargetName="ellipse2" 
                                Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                >
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>

                        <Storyboard x:Key="Animation3" BeginTime="00:00:00.6" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames 
                                Storyboard.TargetName="ellipse3" 
                                Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                >
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>

                        <Storyboard x:Key="Animation4" BeginTime="00:00:00.8" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames 
                                Storyboard.TargetName="ellipse4" 
                                Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                >
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>

                        <Storyboard x:Key="Animation5" BeginTime="00:00:01.0" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames 
                                Storyboard.TargetName="ellipse5" 
                                Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                >
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>

                        <Storyboard x:Key="Animation6" BeginTime="00:00:01.2" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames 
                                Storyboard.TargetName="ellipse6" 
                                Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                >
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>

                        <Storyboard x:Key="Animation7" BeginTime="00:00:01.4" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames 
                                Storyboard.TargetName="ellipse7" 
                                Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                >
                                <SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
                                <SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>
                    </ControlTemplate.Resources>

                    <ControlTemplate.Triggers>
                        <Trigger Property="IsVisible" Value="True">
                            <Trigger.EnterActions>
                                <BeginStoryboard Storyboard="{StaticResource Animation0}" x:Name="Storyboard0" />
                                <BeginStoryboard Storyboard="{StaticResource Animation1}" x:Name="Storyboard1"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation2}" x:Name="Storyboard2"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation3}" x:Name="Storyboard3"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation4}" x:Name="Storyboard4"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation5}" x:Name="Storyboard5"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation6}" x:Name="Storyboard6"/>
                                <BeginStoryboard Storyboard="{StaticResource Animation7}" x:Name="Storyboard7"/>
                            </Trigger.EnterActions>

                            <Trigger.ExitActions>
                                <StopStoryboard BeginStoryboardName="Storyboard0"/>
                                <StopStoryboard BeginStoryboardName="Storyboard1"/>
                                <StopStoryboard BeginStoryboardName="Storyboard2"/>
                                <StopStoryboard BeginStoryboardName="Storyboard3"/>
                                <StopStoryboard BeginStoryboardName="Storyboard4"/>
                                <StopStoryboard BeginStoryboardName="Storyboard5"/>
                                <StopStoryboard BeginStoryboardName="Storyboard6"/>
                                <StopStoryboard BeginStoryboardName="Storyboard7"/>
                            </Trigger.ExitActions>
                        </Trigger>
                    </ControlTemplate.Triggers>

                    <Border
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}" 
                        Background="{TemplateBinding Background}"
                        >
                        <Canvas Height="60" Width="60">
                            <Canvas.Resources>
                                <Style TargetType="Ellipse">
                                    <Setter Property="Width" Value="15"/>
                                    <Setter Property="Height" Value="15" />
                                    <Setter Property="Fill" Value="#009B9B9B" />
                                </Style>
                            </Canvas.Resources>

                            <Ellipse x:Name="ellipse0" Canvas.Left="1.75" Canvas.Top="21"/>
                            <Ellipse x:Name="ellipse1" Canvas.Top="7" Canvas.Left="6.5"/>
                            <Ellipse x:Name="ellipse2" Canvas.Left="20.5" Canvas.Top="0.75"/>
                            <Ellipse x:Name="ellipse3" Canvas.Left="34.75" Canvas.Top="6.75"/>
                            <Ellipse x:Name="ellipse4" Canvas.Left="40.5" Canvas.Top="20.75" />
                            <Ellipse x:Name="ellipse5" Canvas.Left="34.75" Canvas.Top="34.5"/>
                            <Ellipse x:Name="ellipse6" Canvas.Left="20.75" Canvas.Top="39.75"/>
                            <Ellipse x:Name="ellipse7" Canvas.Top="34.25" Canvas.Left="7" />
                            <Ellipse Width="39.5" Height="39.5" Canvas.Left="8.75" Canvas.Top="8" Visibility="Hidden"/>
                        </Canvas>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

上記コードをBusyAnimationStyle.xamlとして保存し、App.xamlにBusyAnimationStyle.xamlを書き込みます。

<Application x:Class="Example.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Windows\BusyAnimationStyle.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

次に、Windowのxamlに配置します。VisibilityはHiddenにしておかないと、アプリケーション起動時にいきなりBusyAnimationが有効になってしまいます。

<Window x:Class="Example.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Height="500" Width="500">
    <Grid>
        <Control Name="busyAnimation" Style="{StaticResource BusyAnimationStyle}" Width="{Binding Path=Width, ElementName=MainWindow}" Visibility="Hidden"/>
    </Grid>
</Window>

以上で準備は完了です。あとは時間のかかる処理を実行する前に下記のようにVisibilityをVisibleにすればアニメーションがスタートします。時間のかかる処理が完了した後は再度VisibilityをHiddenにすることを忘れないようにして下さい。

busyAnimation.Visibility=Visible;

【C#】 平滑化スプラインを実装する

波形に乗ったノイズを取り除く方法はいくつかあるのですが、今回は平滑化スプラインを実装します。ソースはどこかの論文に書いてあったものをC#に移植したものです。まずは平滑化スプラインを行うSplineSmoothingクラスを作成します。

public class SplineSmoothing
{
    private static double[] _x;
    private static double[] _y;
    private static double[] _dy;
    private static double[] _a;
    private static double[] _b;
    private static double[] _c;
    private static double[] _d;
    private static int _n1;
    private static int _n2;

    /// <summary>
    /// スムージング+内挿したい場合に呼び出すコンストラクタ
    /// </summary>
    /// <param name="x">xの値</param>
    /// <param name="y">xでのyの値</param>
    /// <param name="s">ペナルティ係数(大きい程滑らかになる)</param>
    public SplineSmoothing(IList<double> x, IList<double> y, double s)
    {
        Initialize(x.ToArray(), y.ToArray(), s);
    }

    /// <summary>
    /// スムージングを実行するメソッド
    /// </summary>
    /// <param name="y">yの値</param>
    /// <param name="s">ペナルティ係数(大きい程滑らかになる)</param>
    /// <returns></returns>
    public static double[] Smooth(IList<double> y, double s)
    {
        var x = new double[y.Count];
        for (int i = 0; i < x.Length; ++i)
        {
            x[i] = i;
        }
        Initialize(x, y.ToArray(), s);
        var smoothed = new double[y.Count];
        for (int i = 0; i < smoothed.Length; ++i)
        {
            smoothed[i] = _Interpolate(x[i]);
        }
        return smoothed;
    }

    /// <summary>
    /// 初期化を実行するメソッド
    /// </summary>
    /// <param name="x">xの値</param>
    /// <param name="y">yの値</param>
    /// <param name="s">ペナルティ係数(大きい程滑らかになる)</param>
    private static void Initialize(double[] x, double[] y, double s)
    {
        _x = new double[x.Length + 2];
        _y = new double[_x.Length];
        _dy = new double[_x.Length];
        for (int i = 1; i < _x.Length - 1; ++i)
        {
            _x[i] = x[i - 1];
            _y[i] = y[i - 1];
            _dy[i] = 1.0;
        }
        _n1 = 1;
        _n2 = _x.Length - 2;

        int m1 = _n1 - 1;
        int m2 = _n2 + 1;
        var r = new double[_x.Length];
        var r1 = new double[_x.Length];
        var r2 = new double[_x.Length];
        var u = new double[_x.Length];
        double p;
        r[m1] = r[_n1] = r1[_n2] = r2[_n2] = r2[m2] = u[m1] = u[_n1] = u[_n2] = u[m2] = p = 0.0;
        m1 = _n1 + 1;
        m2 = _n2 - 1;
        _a = new double[_x.Length];
        var t = new double[_x.Length];
        var t1 = new double[_x.Length];
        double h = _x[m1] - _x[_n1];
        double f = (_y[m1] - _y[_n1]) / h;
        double g = 0.0;
        double e;
        for (int i = m1; i <= m2; ++i)
        {
            g = h;
            h = _x[i + 1] - _x[i];
            e = f;
            f = (_y[i + 1] - _y[i]) / h;
            _a[i] = f - e;
            t[i] = 2.0 * (g + h) / 3.0;
            t1[i] = h / 3.0;
            r2[i] = _dy[i - 1] / g;
            r[i] = _dy[i + 1] / h;
            r1[i] = -_dy[i] / g - _dy[i] / h;
        }
        _b = new double[_x.Length];
        _c = new double[_x.Length];
        _d = new double[_x.Length];
        for (int i = m1; i <= m2; ++i)
        {
            _b[i] = r[i] * r[i] + r1[i] * r1[i] + r2[i] * r2[i];
            _c[i] = r[i] * r1[i + 1] + r1[i] * r2[i + 1];
            _d[i] = r[i] * r2[i + 2];
        }
        double f2 = -s;

        int count = 0;
        while (true)
        {
            if (++count > 10000) break;

            for (int i = m1; i <= m2; ++i)
            {
                r1[i - 1] = f * r[i - 1];
                r2[i - 2] = g * r[i - 2];
                r[i] = 1 / (p * _b[i] + t[i] - f * r1[i - 1] - g * r2[i - 2]);
                u[i] = _a[i] - r1[i - 1] * u[i - 1] - r2[i - 2] * u[i - 2];
                f = p * _c[i] + t1[i] - h * r1[i - 1];
                g = h;
                h = _d[i] * p;
            }
            for (int i = m2; i >= m1; --i)
            {
                u[i] = r[i] * u[i] - r1[i] * u[i + 1] - r2[i] * u[i + 2];
            }
            e = h = 0.0;
            var v = new double[_x.Length + 2];
            for (int i = _n1; i <= m2; ++i)
            {
                g = h;
                h = (u[i + 1] - u[i]) / (_x[i + 1] - _x[i]);
                v[i] = (h - g) * _dy[i] * _dy[i];
                e = e + v[i] * (h - g);
            }
            g = v[_n2] = -h * _dy[_n2] * _dy[_n2];
            e = e - g * h;
            g = f2;
            f2 = e * p * p;
            if (f2 >= s || f2 <= g)
            {
                for (int i = _n1; i <= _n2; ++i)
                {
                    _a[i] = _y[i] - p * v[i];
                    _c[i] = u[i];
                }
                for (int i = _n1; i <= m2; ++i)
                {
                    h = _x[i + 1] - _x[i];
                    _d[i] = (_c[i + 1] - _c[i]) / (3 * h);
                    _b[i] = (_a[i + 1] - _a[i]) / h - (h * _d[i] + _c[i]) * h;
                }
                break;
            }
            else
            {
                f = 0.0;
                h = (v[m1] - v[_n1]) / (_x[m1] - _x[_n1]);
                for (int i = m1; i <= m2; ++i)
                {
                    g = h;
                    h = (v[i + 1] - v[i]) / (_x[i + 1] - _x[i]);
                    g = h - g - r1[i - 1] * r[i - 1] - r2[i - 2] * r[i - 2];
                    f = f + g * r[i] * g;
                    r[i] = g;
                }
                h = e - p * f;
                if (h <= 0.0)
                {
                    for (int i = _n1; i <= _n2; ++i)
                    {
                        _a[i] = _y[i] - p * v[i];
                        _c[i] = u[i];
                    }
                    for (int i = _n1; i <= m2; ++i)
                    {
                        h = _x[i + 1] - _x[i];
                        _d[i] = (_c[i + 1] - _c[i]) / (3 * h);
                        _b[i] = (_a[i + 1] - _a[i]) / h - (h * _d[i] + _c[i]) * h;
                    }
                    break;
                }
                else
                {
                    p = p + (s - f2) / ((Math.Sqrt(s / e) + p) * h);
                }
            }
        }
    }

    /// <summary>
    /// xにおけるyの値を返すメソッド
    /// </summary>
    /// <param name="x">xの値</param>
    /// <returns>yの値</returns>
    public double Interpolate(double x)
    {
        return _Interpolate(x);
    }

    /// <summary>
    /// xにおけるyの値を返すメソッド
    /// </summary>
    /// <param name="x">xの値</param>
    /// <returns>yの値</returns>
    private static double _Interpolate(double x)
    {
        for (int i = _n1; i < _n2; ++i)
        {
            if ((x >= _x[i]) && (x < _x[i + 1]))
            {
                double h = x - _x[i];
                return ((_d[i] * h + _c[i]) * h + _b[i]) * h + _a[i];
            }
        }

        if (x == _x[_n2])
        {
            double h = x - _x[_n2];
            return ((_d[_n2] * h + _c[_n2]) * h + _b[_n2]) * h + _a[_n2];
        }

        throw new ArgumentOutOfRangeException("x", "Extrapolation is not allowed");
    }
}


次にSplineSmoothingの使い方ですが、Sin波に乗ったノイズを取り除く例を示します。まず、内挿が必要無ければ以下のように簡単に記述できます。

// ノイズ付きのSin波を作成する
var xs = new double[100];
var ys = new double[100];
var random = new Random(1);
for (int i = 0; i < xs.Length; ++i)
{
    double x = 2.0 * Math.PI / xs.Length * i;
    xs[i] = x;
    ys[i] = Math.Sin(x) + (random.NextDouble() - 0.5)/5.0;
}

// スムージングする
double penalty = 0.5;
var smoothedYs = SplineSmoothing.Smooth(ys, penalty);

f:id:ni4muraano:20170416112551j:plain:w250 f:id:ni4muraano:20170416112602j:plain:w250

スムージングに加えて内挿が必要な場合は、まずはコンストラクタでSplineSmoothingクラスを生成し、その後Interpolateメソッドで内挿点を指定すれば良いです。

// ノイズ付きのSin波を作成する
var xs = new double[100];
var ys = new double[100];
var random = new Random(1);
for (int i = 0; i < xs.Length; ++i)
{
    double x = 2.0 * Math.PI / xs.Length * i;
    xs[i] = x;
    ys[i] = Math.Sin(x) + (random.NextDouble() - 0.5)/5.0;
}

// SplineSmoothingクラスを生成する
double penalty = 0.5;
var spline = new SplineSmoothing(xs, ys, penalty);
// 内挿点を指定する
double interpolation = 2.0 * Math.PI / 95.0;
// 内挿点におけるスムージングされたyの値を取得する
double y = spline.Interpolate(interpolation);

【Python】 OSに依存しないフォルダ・ファイルパスの書き方

ディープラーニング関連の論文あるネットワークを試してみたい時、誰かが既に作成してくれていることが多くて助かります。ただ、ソースを自分の環境に落として動かそうとするとエラーになることも多く、原因の一部はOSに依存した書き方があったため、ということもあります。最近あったのはLinuxWindowsでのパスの書き方の違いによるものだったので、対処法をメモします。

元のソースの例

weight_file_path = 'folder_weight\\weights.hdf5'
model.load_weight(weight_file_path)


変更例1

import os

weight_file_path = os.path.join('folder_weight', 'weights.hdf5')
model.load_weight(weight_file_path)


変更例2

import os

weight_file_path = 'folder_weight' + os.sep + 'weights.hdf5'
model.load_weight(weight_file_path)

【OpenCV】 輪郭を強調する

先鋭化フィルタを適用して輪郭を強調する例をメモします。OpenCVには用意されていない関数ですが、フィルタを自作して適用することで簡単に作成することが出来ます。作成するフィルタは以下の通りです(http://www.wakayama-u.ac.jp/~chen/education/image/2012/L8.pdf)。

4近傍

$$ \left( \begin{array}{ccc} 0 & -1 & 0 \\ -1 & 5 & -1 \\ 0 & -1 & 0 \\ \end{array} \right) $$

8近傍

$$ \left( \begin{array}{ccc} -1 & -1 & -1 \\ -1 & 9 & -1 \\ -1 & -1 & -1 \\ \end{array} \right) $$

// 画像を読み込む
Mat source;
imread("picture.jpg").copyTo(source);
if (source.empty())
{
    throw runtime_error("Failed to open image");
}

// 先鋭化フィルタを作成する
const float k = -1.0;
Mat sharpningKernel4 = (Mat_<float>(3, 3) << 0.0, k, 0.0, k, 5.0, k, 0.0, k, 0.0);
Mat sharpningKernel8 = (Mat_<float>(3, 3) << k, k, k, k, 9.0, k, k, k, k);

// 先鋭化フィルタを適用する
Mat destination;
cv::filter2D(source, destination, source.depth(), sharpningKernel8);

imshow("source", source);
imshow("destination", destination);

f:id:ni4muraano:20170415170026j:plain:w200 f:id:ni4muraano:20170415170032j:plain:w200

【WPF】 二次元配列をWriteableBitmapに変換する

二次元配列をWriteableBitmapに変換する方法をメモします。グレースケール画像をbyteの二次元配列で表現しており、これをImageクラスに表示するためWriteableBitmapに変換したかった、というのが動機になります。

using System.Windows.Media;
using System.Windows.Media.Imaging;

private WriteableBitmap ToWriteableBitmap(byte[,] image)
{
    int width = image.GetLength(1);
    int height = image.GetLength(0);
    var bitmap = new WriteableBitmap(width, height, 96.0, 96.0,
                                     PixelFormats.Gray8,
                                     BitmapPalettes.Gray256);
    int stride = width*1;
    byte[] pixels = image.Cast<byte>().ToArray();
    bitmap.WritePixels(new Int32Rect(0, 0, width, height), pixels, stride, 0);
    
    return bitmap;
}

上記のToWriteableBitmapの返り値をImageのSourceプロパティに代入することで画像を表示することが出来ます。

【C#】 二次元配列を一次元配列に変換する

タイトルの通り二次元配列を一次元配列に変換する方法です。

using System.Linq;

var array2dim = new byte[,] { { 0, 1, 2 },
                              { 3, 4, 5 },
                              { 6, 7, 8 } };
// array1dimは{0, 1, 2, 3, 4, 5, 6, 7, 8}となる
byte[] array1dim = array2dim.Cast<byte>().ToArray();