【C#エラー】System.BadImageFormatException 間違ったフォーマットのプログラムを読み込もうとしました。

ライブラリ(DLL)を使ったアプリケーションを作成しようとすると、

 

アプリケーションはブレークモードになっています

System.BadImageFormatException: ‘間違ったフォーマットのプログラムを読み込もうとしました。

 

 

と表示される場合があります。

このBadImageFormatという部分がDLLが32bitもしくは64bitで作られたものを、C#アプリケーションから異なるbit(32bit)で作成し、DLLを参照した場合にこのエラーが表示されます。

最近のVisual Studioでは、C#のアプリケーションはデフォルトで32bitで作成されるため、とくにC言語で作られた64bitのDLLを使用している場合に、このエラーが発生しやすいです。

 

回避方法は、Visual Studioでプロジェクト名を右クリックし、プロパティを選択します。

表示されたプロパティ画面において、左側のビルドを選択し、右側に表示されている 32ビットを選ぶ のチェックを外します。

構成の部分でDebugReleaseの設定があるので、両方のチェックを外してください。

 

この状態で、再度ビルドを行うと、間違ったフォーマットのプログラムを読み込もうとしました。というエラーメッセージは表示されなくなります。

 

この32ビットを選ぶ の設定はVisual Studioのバージョンによって異なります。

少なくともVisual Studio 2019以降では、32ビットを選ぶが初期設定となっているので、最近のVisual StudioでDLLを使ったアプリを作成する場合は注意が必要です。

【C#エラー】ファイルForm.resxを処理できませんでした。インターネットまたは制限付きゾーン内にあるか、ファイルにWebマークがあるためです。

メールに添付したプログラムをVisual Studioで開き、ビルドをすると以下のようなメッセージが表示されました。

 

ファイルForm.resxを処理できませんでした。インターネットまたは制限付きゾーン内にあるか、ファイルにWebマークがあるためです。これらのファイルを処理するにはWebマークを削除してください。

 

Visual StudioでForm.resxファイルのプロパティを探してもWebマークらしきものがありません。

 

そこで、エクスプローラーでエラーの出ているファイル(ここではForm1.resx)を右クリックし、プロパティを表示し、

 

このファイルは他のコンピューターから取得したものです。このコンピューターを保護するため、このファイルへのアクセスはブロックされる可能性があります。

 

の右側にある 許可する にチェックを入れ、 適用 をクリックします。

 

これで、再度Visual Studioでビルドするとエラーが無くなります。

【C#】各種メモリの最大値(2GB制限)

最近のPCではOSは64ビットで、搭載メモリも8GBぐらいは普通にあるので、C#のプログラムでもメモリを4GBぐらいは普通に確保できそうですが、実際には2BGぐらいで頭打ちになります。

おそらくメモリサイズ(要素数)を計算するときにint型で計算していてint型の最大値(2,147,483,647)を超える事でエラーになる場合が多そうです。

そこで、試しにメモリ確保の処理(配列、Bitmapクラス、Marshal.AllocCoTaskMem)の最大値が、どの程度まで確保できるのか?を確認してみました。

評価環境

  • Windows11 Home 64bit
  • 搭載メモリ 32GB
  • Visual Studio 2019
  • C#(.NET Core 3.1)

 

配列の最大値

よく使うbyte, int, float, doubleの型で試してみたところ、以下の要素数が最大となりました。

var byteArr = new byte[2147483591];
var intArr= new int[2146435071];
var floatArr= new float[2146435071];
var doubleArr= new double[2146435071];

配列の最大値に関しては、メモリのサイズというよりも、要素数のint型の最大値制限になっているようです。

.NET Frameworkの場合、デフォルトでは最大サイズが異なるようです。
詳細は、下記ページを参照ください。

https://docs.microsoft.com/ja-jp/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element?redirectedfrom=MSDN

Bitmapクラスの最大値

Bitmapクラスの確保には、幅、高さ、PixelFormatの組み合わせが、いろいろできてしまうため、幅の値は固定して、高さを変えながらエラーが出るまで最大の高さを確認しました。

以下が、PixelFormatをFormat8bppIndexed, Format24bppRgb, Format32bppArgbで確認したときの最大のサイズとなります。

var bmp8 = new Bitmap(1024 * 3, 699049, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
var bmp24 = new Bitmap(1024, 699049, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
var bmp32 = new Bitmap(1024, 524287, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

Bitmapクラスに関しては、 幅 x 高さ x 画素のバイト数(1, 3, 4) の値がint型の最大値に引っかかっているようです。

 

AllocCoTaskMemの最大値

あまり使う機会はありませんが、メモリ確保で使われるAllocCoTaskMemについても調べてみました。

var ptr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(2147483647);

こちらは、AllocCoTaskMemの引数がint型のため、最大値を超えるとビルド時にエラーとなります。

 

まとめ

C#のメモリの最大値について調べてみましたが、おおむね2GBの制限があります。

メモリのサイズというより、要素数のint型の最大値(2,147,483,647)を超える事によりエラーになっているようです。

今時のPCで、2GBしかメモリを確保できないだなんて。。

1つのプロセス(実行しているプログラム)で2GB以下のメモリを複数確保する事は可能です。

どうしても2GB以上のメモリを確保したい場合は、メモリを分割して処理するか、C言語ライブラリを作成して、メモリ管理をライブラリ側で行う事で可能になります。
ただし、この時にも、うっかりint型で width * height * ch みたいな計算をしてしまうと、int型の最大値を超えてしまうので、要素数やメモリサイズを計算するときの型には注意が必要です。

 

参考

https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/integral-numeric-types

 

【.NET】C#でSIMDを使った高速演算

私は、.NET Frameworkを使う事が多いのですが、.NET FrameworkからSIMDを使うにはSystem.Numerics.Vectorsクラスを使ってSIMD演算ができるのですが、C言語で使うSIMDとは全く別物で使っていませんでした。

ところが、.NET Core 3.0から使えるSystem.Runtime.Intrinsics.X86 名前空間は、ほぼC言語のSIMDと似ているので、.NET 5.0 も登場した事だし、少し触ってみました。

 

そもそもSIMDって何?という話は私も説明できる程は理解していませんが、8bitの型(byteなど)や16bitの型(shortなど)を128bitや256bitでまとめて演算する事で、高速に処理する事が可能となります。

まずは、C言語の説明になりますが、ここ↓を見る事をおススメします。

 

C#のSIMDで出来る事は、加算、減算、シフトやビットの入れ替えなどになりますが、ここ↓を参照下さい。

https://docs.microsoft.com/ja-jp/dotnet/api/system.runtime.intrinsics.x86.avx2?view=net-5.0

 

ushort型配列をビットシフトし、byte配列へ代入する例

ushort型(16bit)の配列に格納された10bitの値(0~1023)を2bitシフトして、8bit(byte型)の配列に代入する例を示します。

 

SIMDのプログラムでは、ポインタを使うので、プロジェクトのプロパティで、アンセーフコードの許可にチェックを入れます。

 

まずは、作成したコンソールアプリのサンプルをご覧ください。

using System;
using System.Runtime.Intrinsics.X86;

namespace ConsoleApp1
{
    class Program
    {
        static unsafe void Main(string[] args)
        {
            // データの個数(16の倍数の個数)
            int dataCount = 1024;

            // ushort型(16bit)の配列
            var usData = new ushort[dataCount];
            // byte型(bit)の配列
            var bData = new byte[dataCount];

            // 評価用データの作成
            for (int i = 0; i < dataCount; i++)
            {
                usData[i] = (ushort)i;
            }

            fixed (ushort* pusData = usData)
            fixed (byte* pbData = bData)
            {
                for (int i = 0; i < dataCount; i += 16)
                {
                    // データをポインタから読み込む
                    var vVal1 = Avx2.LoadVector128((short*)pusData + i);
                    var vVal2 = Avx2.LoadVector128((short*)pusData + i + 8);
                    // 2ビット右側へシフト
                    vVal1 = Avx2.ShiftRightLogical(vVal1, 2);
                    vVal2 = Avx2.ShiftRightLogical(vVal2, 2);
                    // パック
                    var vDst = Avx2.PackUnsignedSaturate(vVal1, vVal2);
                    // 結果をbyte型配列に書き込む
                    Avx2.Store(pbData + i, vDst);
                }
            }

            // 結果出力
            for (int i = 0; i < bData.Length; i++)
            {
                Console.Write($"{bData[i],3}, ");
            }
        }
    }
}

今回は、AVX2を使っていますが、AVX2は第4世代Core(Haswell)以降のCPUであれば使用可能なので、今どきのPCであれば、ほとんど使用できると思います。

 

まず、LoadVector128メソッドで、ushort配列(16bit)のポインタを128bitのVetorと呼ばれる高速に読み書きできる領域へ読込しています。
16bitのデータを128bitへ読込しているので、この1回のメソッドで、128 / 16 = 8 の8個分のデータを読込しています。
ちなみに、ushortポインタ(ushort*)をshortポインタ(short*)へキャストしているのは、最後に出てくるPackUnsignedSaturateへ渡す引数にVector128<ushort>が無い(Vector128<short>はある)ため、short*へキャストしています。

var vVal1 = Avx2.LoadVector128((short*)pusData + i);

ShiftRightLogicalメソッドでは、右側へシフトしています。
下記の例は2ビット右側へシフトしています。(4で割るのと同じ)

vVal1 = Avx.ShiftRightLogical(vVal1, 2);

PackUnsignedSaturateメソッドでは、パックと呼ばれる処理になりますが、詳細は最初に示した参考文献を見て頂くのがいいかと思いますが、引数で渡された型の半分の下位ビットをつなぎ合わせた物を返します。

var vDst = Avx2.PackUnsignedSaturate(vVal1, vVal2);

 

今回の例では、最初の引数の8個の16bitの値のそれぞれ、下位8bitの部分を取得し、8個の8bitの値とし、第二引数も同様に8個の8bitの値を取得し、計16個の8bitの値(16 x 8 = 128bit)を返します。

 

StoreメソッドはVectorから配列を示しているポインタへ値を戻します。

Avx2.Store(pbData + i, vDst);

まとめ

現状では、.NETのSIMDの情報が少ないので、C言語のSIMDの情報を頼りにする事になるかと思いますが、今回、Vectorと言っていた部分をレジスタに読み替えれば、ある程度理解できると思います。

また、このVectorへ渡すメモリにアライメントという概念があり、LoadVector128と似たメソッドにLoadAlignedVector128というメソッドがあり、これを使うと処理が少し速くなるのですが、メモリのアドレス(番地)が128で割り切れる必要があり、通常、配列から取得したポインタのアドレスは、このアドレスが128では割り切れない番地になっているため、少し工夫しないと、そのままでは使用する事ができません。

 

 

 .NET 5 C# Windows Forms プログラム へ戻る

 

【.NET C# Windows Forms】ユーザーコントロールプロジェクトの作成

.NET 5.0以降の.NETで、.NET Frameworkで作っていたユーザーコントロールライブラリの作成方法を調べてみました。

最終的には下図のように、Formアプリケーション作成時のツールボックスに作成中のUserControlが表示される事を目標としてます。

まず、ユーザーコントロール用のプロジェクトを作成するため、ソリューションエクスプローラのソリューション名の部分を右クリックし、追加→新しいプロジェクト でユーザーコントロール用のプロジェクトを追加します。

しかし、ここでちょっとした不都合があり、プロジェクトのテンプレートにWindows フォームコントロールライブラリ(.NET Framework)はあるものの、Windows フォームコントロールライブラリ(.NET)が、ありません。

(2021.8.3) 新しいバージョンのVisual Studioでは Windows フォームコントロールライブラリが追加されました。そのためVisual Studioを更新し、Windows フォームコントロールライブラリを選択してコントロールライブラリを作成してください。

下図はVisual Studio Community 2019 Ver.16.10.4の画面です。


以下は旧バージョン時の説明です。

仕方がないので、Windows Forms App(.NET)を選択し次へをクリックします。
(最終的にユーザーコントロールライブラリに修正します)

次に表示されたウィンドウで、プロジェクト名に適当な名前を指定し、作成をクリックします。

作成したプロジェクト名を右クリックし、 追加 → ユーザーコントロール(Windowsフォーム) をクリックします。

今回は、名前をデフォルトのままにしていますが、適切なコントロールの名前を付けて追加をクリックします。

プロジェクト名を右クリックし、プロパティをクリックし、アプリケーションを出力の種類Windowsアプリケーション から クラスライブラリに変更します。
また、.NET(.NET5.0以降)のユーザーコントロールを作成する場合は、対象のフレームワークも.NET5.0に変更します。

出力の種類をクラスライブラリに変更した時点で、プロジェクトの作成時に作成されていたForm1.cs、Form1.Designer.cs、Form.resx、Program.csは必要なくなるので、削除しておきます。

これで、元々はWindows Formsアプリとして作成されたプロジェクトでしたが、.NET用のユーザーコントロールのプロジェクトとなりました。

このユーザーコントロールを使用する側のWindows Formsのプロジェクトの依存関係を右クリックし、プロジェクト参照の追加をクリックします。

次に表示されたユーザーコントロールのプロジェクト(ここではWindowsFormsApp2)にチェックを入れ、OKボタンをクリックします。

ここで一旦、ソリューションをビルドします。

これで、最初に示したように、ユーザーコントロールを使う側(参照元)のプロジェクトのフォームを表示すると、ツールボックスのウィンドウに作成したユーザーコントロールが表示されます。

 

(注意点)
ここでは、.NETのWindows Formsのプロジェクト(参照元)から、.NETのクラスライブラリのプロジェクト(参照先)を参照していますが、プロジェクトで設定している対象のフレームワークが、参照元のプロジェクトより、参照先のプロジェクトの方が新しいフレームワークを設定している場合、エラーになるので注意してください。

(参考)

【NU1201】プロジェクト XXX は XXX と互換性がありません。

 

 .NET 5 C# Windows Forms プログラム へ戻る

【NU1201】プロジェクト XXX は XXX と互換性がありません。

あるプロジェクトと、別のライブラリなどのプロジェクトに依存関係(プロジェクトの参照)がある場合、プロジェクトで設定している対象のフレームワークが、参照元のプロジェクトより、参照先のプロジェクトが新しい場合、以下のエラーコードが表示されます。

コード 説明
NU1201 プロジェクト XXX は XXX と互換性がありません。プロジェクト XXX がサポートするもの XXX

対象のフレームワークは

.NET 5.0 → .NET Core 3.1 → .NET Core 3.0 → .NET Framework

の順で、参照元のプロジェクトより、参照先のプロジェクトが同じか、右側にあるフレームワークである必要があります。

今のところ、 .NET 5.0 から .NET Framework のプロジェクトを参照する事も出来ますが、出来るだけ使用するフレームワークは合わせておいた方がいいでしょうね。

 

.NET 5 C# Windows Forms プログラム へ戻る

【NETSDK1137】Microsoft.NET.Sdk.WindowsDesktop SDKを使用する必要はなくなりました。

対象のフレームワークを .NET Core から .NET 5.0 を指定すると、以下のような警告が表示されます。

コード 説明
NETSDK1137 Microsoft.NET.Sdk.WindowsDesktop SDKを使用する必要は無くなりました。
ルートプロジェクト要素のSDK属性を’Microsoft.NET.SDK’に変更することをご検討ください。

 

そこで、プロジェクトファイル(*.csproj)をテキストエディタで開くか、Visual Studioで表示されているプロジェクト名をダブルクリックでプロジェクトファイル(*.csproj)を開き、 Project Sdk を

Microsoft.NET.Sdk.WindowsDesktop から Microsoft.NET.Sdk へ変更します。

 

具体的には

【修正前】

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
 <PropertyGroup>
 <OutputType>WinExe</OutputType>
 <TargetFramework>net5.0-windows</TargetFramework>
 <UseWindowsForms>true</UseWindowsForms>
 </PropertyGroup>
</Project>

【修正後】

<Project Sdk="Microsoft.NET.Sdk">
 <PropertyGroup>
 <OutputType>WinExe</OutputType>
 <TargetFramework>net5.0-windows</TargetFramework>
 <UseWindowsForms>true</UseWindowsForms>
 </PropertyGroup>
</Project>

のように修正します。

 

← .NET 5 C# Windows Forms プログラム へ戻る

.NET 5 正式版が公開されました

2020年11月11日(日本時間)、.NET5の正式版が公開されました!!

 

.NET5は以下のページよりダウンロードできます。

https://dotnet.microsoft.com/download/dotnet/5.0

 

対応のVisual Studio は Visual Studio 2019 Ver.16.8以降でこちらから入手できます。

すでにVisual Studio2019をインストール済みの場合、メニューの ヘルプ→更新プログラムの確認 よりアップデートしてください。

https://visualstudio.microsoft.com/ja/

 

(参考情報)

Announcing .NET 5.0

 

← .NET 5 C# Windows Forms プログラム へ戻る

.NET Frameworkから.NET5への移植作業(Windows Formsの場合)

.NET Frameworkで作成していたプログラムを.NET5への移植作業方法を手探りしながら調べているのですが、そのメモ状態の情報です。(随時、修正中です。)

そもそも、.NET5は従来の.NET Coreの流れを組んでいるため、.NET Frameworkで作成したプロジェクトのプロパティで、対象のフレームワーク.NET5 を選ぶ事ができません。

 

.NET Frameworkから.NET5に移行するにあたって、プロジェクトファイル(*.csproj)が大きく変更されています。このプロジェクトファイルを何とかすれば、移植が可能なようです。

 

そこで、移植評価のサンプルとして、私がよく使う構成の.NET Frameworkのプロジェクトを用意しました。

フォームには

  • ToolStripMenuItem
  • StatusStrip
  • SplitContainer
  • PictureBox
  • Label
  • TextBox
  • Button

を配置し、TextBoxのTextプロパティは前回の状態を保持できるように、PropertyBindingを設定しました。

 

移植作業は

  1. .NET 5.0 との互換性を調べる
  2. プロジェクトファイルの差し替え
  3. アプリケーションの設定
  4. 参照の追加
  5. App.configファイルの修正

の手順で行います。

.NET Frameworkから .NET 5.0 へ変換するための互換性を調べる

.NET Frameworkから .NET 5.0 への互換性を調べるには、 .NET Portability Analyzer というツールを使います。

このツールを使うには、Visual Studio のメニューの 拡張機能 → 拡張機能の管理 をクリックします。

表示されたウィンドウの オンライン を選択し、右上の検索ボックスに .NET Portability と入力すると
.NET Portability Analyzer がみつかるので、これをダウンロードしインストール(Modifyボタンをクリック)します。

インストールが完了すると、Visual Studioのメニューの 分析 に追加されますが、まずは、ツールの設定を行うため、 Portability Analyzer Setting をクリックします。

今回は、 .NET 5.0への互換性を調べるため、 .NET の 5.0 にチェックを入れます。
また、Output formatsは、好みもあると思いますが、 HTML が見やすいので、これにチェックを入れます。

上図のようにチェックを入れたら、OKボタンをクリックし、メニューの Analyze Assembly Portability をクリックします。

すると、プロジェクトが出力する実行ファイル(*.exe)〔ライブラリの時はdllファイル〕を聞かれるので、exeファイル もしくは dllファイルを選択し、開く をクリックします。

すると、作成された互換性解析結果のファイルが表示されるので、 Open Report をクリックします。

解析結果は以下のように表示されます。

これを見ると、 .NET 5.0 への互換性は100%
100%でない場合は、まだ見た事がないのですが、互換性が無いと、 Recommend changes に何か表示されるのかな??

これで、 .NET 5.0 への互換性は100%あると確認できたので、移植作業を行います。

※移植作業のため、.NET Frameworkのプロジェクト一式すべて、.NET 5.0用にファイルをコピーします。
(旧.NET Frameworkのプロジェクトも構成を調べるのに使うので、とっておいてください)

プロジェクトファイル(*.csproj)の差し替え

コピーしたプロジェクトを Visual Studio で開き、プロジェクトファイル(*.csproj)を編集するのですが、そのままでは編集できないので、ソリューションエクスプローラのプロジェクト名を右クリックし、プロジェクトのアンロードを行います。

プロジェクトのアンロードを行うと、プロジェクトファイルの中身が表示されます。

.NET Frameworkのプロジェクトファイルの中身は、上図のように、いろいろと記載されているのですが、.NET 5.0 では、とりあえず以下の内容に差し替えます。

<Project Sdk="Microsoft.NET.Sdk">
 <PropertyGroup>
  <OutputType>WinExe</OutputType>
  <TargetFramework>net5.0-windows</TargetFramework>
  <UseWindowsForms>true</UseWindowsForms>
  <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
 </PropertyGroup>
</Project>

ただし、プロジェクトの出力ファイルが実行ファイル(*.exe)ではなく、ライブラリ(*.dll)の場合は

<OutputType>WinExe</OutputType>

の行は削除します。

差し替え後のプロジェクトファイルは下図のように、不安になるぐらいスッキリとしていますが、不足している物もあるので、次項で修正していきます。

プロジェクトファイルの中身を差し替えたら、アンロードしたプロジェクト名を右クリックし、プロジェクトの再読み込みを行います。

アプリケーションの設定

プロジェクト名を右クリックし、プロパティで表示されるアプリケーションの設定は、プロジェクトファイルを差し替えた事で、.NET Frameworkで行っていた設定が消えている可能性があるため、必要に応じて設定してください。

出力されたファイル(*.exe、*.dll)のプロパティで表示される会社名やバージョンなどは、.NET Frameworkの時は、アプリケーションの設定のページに アセンブリ情報 というボタンがありましたが、 .NET 5.0 では、パッケージという項目へ移動したので、この内容も間違いないか?確認してください。

 

参照の追加

.NET Frameworkのプロジェクトの 参照 に追加していたプロジェクトやライブライへの参照を追加します。

.NET 5.0 でのプロジェクトでは、 参照 から 依存関係 に表示が変更されていますが、この 依存関係 を右クリックし、プロジェクト参照の追加 をクリックします。

ここで、従来の参照と同じ様に、プロジェクトやライブラリの参照を追加します。

依存関係の参照にライブラリやプロジェクトを追加すると以下のように、.NET Frameworkの時の参照からは少し変更された表示になっています。

 

App.configファイルの修正

プロジェクトに App.config ファイルがある場合は、このファイルを修正します。

ソリューションエクスプローラの App.config ファイルを選択し、<supportedRuntime>の部分(下図の青く選択された部分)を削除します。

 

動作確認

ここまで修正を行い、ソリューションのビルドを行うと、フォームのアイコンが変更され、.NET 5.0 に移植出来た事が確認できます。

 

その他

.NET Frameworkから.NET5へ移植すると、SplitContainerの表示が少し崩れていました。
また、.NET Frameworkでは、SplitContainerのSplitterの位置は、フォームエディタの表示で、マウスのドラッグで移動する事ができましたが、.NET5では、それが出来なくなっていたので、SplitterDistanceプロパティで調整してみたりと、他にも少しずつ、.NET Frameworkとは異なる点があるとは思います。

他にも、.NET Frameworkのソリューションで、

・Windows Formsのプロジェクト
・クラスライブラリのプロジェクト
・ユーザーコントロールのプロジェクト

を作り、Windows Formsのプロジェクトから、クラスライブラリ、ユーザーコントロールのプロジェクトを参照し、Windows Formsのプロジェクトだけを.NET 5.0に変更し、他は.NET Frameworkのままにしてみたのですが、動作はするようでした。
つまりは、.NET 5.0 のプロジェクトから、.NET Frameworkのライブラリを使う事が出来たのですが、.NET と .NET Framework が混在出来るのは、今だけ?かもしれないので、どうせ.NET へ移植するのなら、すべてを移植するようにした方が良いでしょうね?

参考ページ

https://docs.microsoft.com/ja-jp/dotnet/core/porting/winforms

 

 .NET 5 C# Windows Forms プログラミング へ戻る

.NET用行列演算クラスライブラリ(行列の積、逆行列、転置行列、擬似逆行列など)

.NETには標準でMatrixクラス(名前空間:System.Drawing.Drawing2D)がありますが、このクラスはアフィン変換用に作られ、3行2列の行列に限定されているため、汎用的な行列演算ができません。

汎用的な行列演算ができると、アフィン変換に限らず、射影変換や擬似逆行列を用いた近似処理も可能になります。

そこで日頃から二次元配列をそのまま行列に使えればいいのに!と思っていたのですが、PythonのOenCVっぽく簡単に?行列(二次元配列)の計算ができる.NET Framework用のImagingSolution.Matクラス(ImagingSolution.Mat.dll)を作成しました。

 

主な機能

行列の積・加算・減算、逆行列、転置行列、擬似逆行列、画像の読込・表示、CSVファイル保存など

 

ダウンロード

ライブラリ(ImagingSolution.Mat.dll)とサンプルコードは以下からダウンロードできます。

公開日 バージョン ファイル 備考
2020.07.25 Ver.0.1.1 ImagingSolution.Mat_V011.zip 暫定版

※ご自由に使用して頂いて構いませんが、使用は自己責任でお願いします。

動作イメージ

サンプルコード

// 行列(matA)の設定(二次元配列で行列を設定)
var matA = new double[,] {
    { 1, 2, 3 },
    { 4, 5, 6 },
    { 7, 8, 9 }
};
matA.Print("matA ="); // コンソールへ行列の表示

// 行列(matB)の設定
var matB = new double[,] {
    { 1, 4, 8 },
    { 6, 2, 5 },
    { 9, 7, 3 }
};
matB.Print("matB =");

// 行列(matA)と行列(matA)の積
var matMult = matA.Mult(matB);
matMult.Print("matA matB =");

// 行列(matA)と行列(matA)の加算
var matAdd = matA.Add(matB);
matAdd.Print("matA + matB =");

// 行列(matA)と行列(matA)の減算
var matSub = matA.Sub(matB);
matSub.Print("matA - matB =");

// 行列(matB)の逆行列
var matInverse = matB.Inverse();
matInverse.Print("(matB)-1 =");

// 行列(matA)の転置
var matTranspose = matA.Transpose();
matTranspose.Print("(matA)T =");

// 回転行列の取得
var matRotate = Mat.RotateMat(10.0);
matRotate.Print("RotateMat =");

// 拡大行列の取得
var matScale = Mat.ScaleMat(2.0, 5.0);
matScale.Print("ScaleMat =");

// 平行移動行列の取得
var matTranslate = Mat.TranslateMat(-5.0, 12.0);
matTranslate.Print("TranslateMat =");

Bitmap bmp;
// 画像ファイルを開く
var imgMat = Mat.Imread("0.bmp", out bmp);
imgMat.Print("Image data =", false);

// 画像の表示
var window = bmp.Imshow("Image");

// 配列(画像データ)をcsvファイルに保存
imgMat.SaveCsv("matData.csv");

実行画面

サンプルはコンソールプログラムで作成しましたが、Formアプリケーションでも使用可能です。

メソッド

Add<T>(T[,], T[,]) 行列の加算を行います。
AddS<T>(T[,], T) 行列の各要素にスカラー値を加算します。
ArrayToMat<T>(T[]) 一次元配列からN行1列の行列(二次元配列)に変換します。
Cast<T1, T2>(T1[,]) 行列の型を変更します。
CloneMat<T>(T[,]) 行列の型を変更します。
Cross<T>(T[,], T[,]) 2つのベクトル(3行1列の配列)の外積を計算します。
Degrees(double) 角度のラジアンから度へ変更します。
Flatten<T>(T[,]) 行列をN行1列へ変換します。
Hadam<T>(T[,], T[,]) 行列の各要素の積(アダマール積)を計算します。
Identity<T>(int) サイズを指定して単位行列を取得します。
Imread(string) 画像データを行列(Byte型二次元配列)に格納します。
Imread(string, System.Drawing.Bitmap) 画像データを行列(Byte型二次元配列)に格納し、Bitmapクラスオブジェクトを取得します。
ImreadPlane(string) 画像データをプレーン(各色ごとの行列)分離し、行列(Byte型二次元配列)に格納します。
ImreadPlane(string, System.Drawing.Bitmap) 画像データをプレーン(各色ごとの行列)分離し、行列(Byte型二次元配列)に格納し、Bitmapクラスオブジェクトを取得します。
Imshow(System.Drawing.Bitmap, ImagingSolution.Mat.NamedWindow) Bitmapクラスオブジェクトと表示先ウィンドウを指定して画像を表示します。
Imshow(System.Drawing.Bitmap, string) Bitmapクラスオブジェクトと表示先ウィンドウタイトルを指定して画像を表示します。
Inverse(double[,]) 逆行列を求めます。
Inverse(float[,]) 逆行列を求めます。
Mult<T>(T[,], T[,]) 行列の積を求めます。
PointToMat(System.Drawing.Point) Point構造体の座標を3行1列の行列に格納します。
PointToMat(System.Drawing.Point[]) Point構造体配列の座標を3行N列の行列に格納します。
PointToMat(System.Drawing.PointF) Point構造体の座標を3行1列の行列に格納します。
PointToMat(System.Drawing.PointF[]) Point構造体配列の座標を3行N列の行列に格納します。
Print<T>(T[,], bool) コンソール画面、もしく出力ウィンドウに行列を値を表示します。
Print<T>(T[,], string, bool) コンソール画面、もしく出力ウィンドウに行列を値を表示します。
PseudoInverse(double[,]) 擬似逆行列を求めます。
PseudoInverse(float[,]) 擬似逆行列を求めます。
Radians(double) 角度の度数(°)からラジアンへ変更
Reshape<T>(T[,], int, int) 行列のサイズ(行数、列数)を変更します。
RotateAtMat(double, double, double) 回転角度、回転の中心座標を指定して回転行列(二次元)を取得します。
RotateAtMat(float, float, float) 回転角度、回転の中心座標を指定して回転行列(二次元)を取得します。
RotateMat(double) 回転角度を指定して回転行列(二次元)を取得します。
RotateMat(float) 回転角度を指定して回転行列(二次元)を取得します。
RotateXMat(double) 回転角度を指定してX軸周りの回転行列(三次元)を取得します。
RotateXMat(float) 回転角度を指定してX軸周りの回転行列(三次元)を取得します。
RotateYMat(double) 回転角度を指定してY軸周りの回転行列(三次元)を取得します。
RotateYMat(float) 回転角度を指定してY軸周りの回転行列(三次元)を取得します。
RotateZMat(double) 回転角度を指定してZ軸周りの回転行列(三次元)を取得します。
RotateZMat(float) 回転角度を指定してZ軸周りの回転行列(三次元)を取得します。
SaveCsv<T>(T[,], string) 行列の値をCSVファイルに保存します。
ScaleAtMat(double, double, double, double) 拡大率、拡大の基点の座標を指定して拡大行列(二次元)を取得します。
ScaleAtMat(float, float, float, float) 拡大率、拡大の基点の座標を指定して拡大行列(二次元)を取得します。
ScaleMat(double, double) 拡大率を指定して拡大行列(二次元)を取得します。
ScaleMat(double, double, double) 拡大率を指定して拡大行列(二次元)を取得します。
ScaleMat(float, float) 拡大率を指定して拡大行列(二次元)を取得します。
ScaleMat(float, float, float) 拡大率を指定して拡大行列(三次元)を取得します。
Sub<T>(T[,], T[,]) 行列の減算を行います。
SubS<T>(T[,], T) 行列の各要素からスカラー値を減算します。
SwapRows<T>(T[,], int, int) 行列の行の値を入れ替えます。
TranslateMat(double, double) 移動量を指定して、平行移動行列(二次元)を取得します。
TranslateMat(double, double, double) 移動量を指定して、平行移動行列(三次元)を取得します。
TranslateMat(float, float) 移動量を指定して、平行移動行列(二次元)を取得します。
TranslateMat(float, float, float) 移動量を指定して、平行移動行列(三次元)を取得します。
Transpose<T>(T[,]) 行列の転置行列を求めます。
NamedWindow.Imshow(System.Drawing.Bitmap) Bitmapクラスオブジェクトを指定し画像表示用ウィンドウに画像を表示します。
NamedWindow.NamedWindow(string) ウィンドウタイトルを指定し画像表示用ウィンドウを作成します。

 

行列の設定

行列には.NET Framework標準の二次元配列を用います。

行列の値を設定は、以下のように行います。

// 行列(matA)の設定例
var matA = new double[3, 3];
matA[0, 0] = 1; matA[0, 1] = 2; matA[0, 2] = 3;
matA[1, 0] = 4; matA[1, 1] = 5; matA[1, 2] = 6;
matA[2, 0] = 7; matA[2, 1] = 8; matA[2, 2] = 9;

// 行列(matB)の設定例
var matB = new double[,] {
    { 1, 4, 8 },
    { 6, 2, 5 },
    { 9, 7, 3 }
};

行列の値の表示

行列(二次元配列)の値はPrint()メソッドを用いるとコンソール画面(フォームアプリの場合は出力ウィンドウ)に配列の型とサイズの行の次に配列の値が表示されます。

コード例

var matA = new double[,] {
    { 1, 2, 3 },
    { 4, 5, 6 },
    { 7, 8, 9 }
};
matA.Print();

出力例(コンソールアプリの場合)

出力例(フォームアプリの場合)

 

Print()メソッドの引数に文字列を渡すと、先頭行に文字列が表示されます。

matA.Print("matA =");

出力例

行列の行数が10行より大きい場合は、途中の行が省略されて表示されます。

省略しない場合は、引き数にfalseを追加します。

コード例

// 先頭行付き表示
matA.Print("matA =", false);
// 先頭行なしの場合
matA.Print(false);

出力例

 

行列の演算

行列演算のメソッドは拡張メソッド形式で形式で作成しています。

好みに合わせてお使い下さい。

コード例

var matA = new double[,] {
    { 1, 2, 3 },
    { 4, 5, 6 },
    { 7, 8, 9 }
};

var matB = new double[,] {
    { 1, 4, 8 },
    { 6, 2, 5 },
    { 9, 7, 3 }
};

// 行列の積(従来の書き方)
var mat1 = Mat.Mult(matA, matB);

// 行列の積(拡張メソッドの書き方)
var mat2 = matA.Mult(matB);

// 逆行列(従来の書き方)
var mat3 = Mat.Inverse(matB);

// 逆行列(拡張メソッドの書き方)
var mat4 = matB.Inverse();

行列の演算では、行列のサイズ(行数、列数)が不正だったり、逆行列が解けない場合、メソッド内で例外が発生します。

例外は嫌な場合は、事前にサイズチェックや、try~catchによるエラー処理を行って下さい。

 

画像の読込、表示、CSVファイル保存

画像の読込、表示、CSVファイル保存を行うには、以下のコードで行います。

コード例

Bitmap bmp;
//画像ファイルを開く
var matImg = Mat.Imread("0.bmp", out bmp);
// 画像データの表示
matImg.Print(false);
// 画像の表示
bmp.Imshow("画像");
// CSVファイル保存
matImg.SaveCsv("image.csv");

画像ファイルをImread()メソッドで開くと、Byte型の二次元配列(Byte[,])に輝度値が格納されます。

カラー画像の場合、B,G,R,B,G,R…の順で輝度値が格納されます。

Imread()メソッドとは別に、ImreadPlane()メソッドで画像ファイルを開くと、各プレーン(色)ごとに二次元配列の配列(Byte[][,])に輝度値が格納されます。プレーンの順番はB,G,Rの順となります。

 

実行結果

 

画像の表示のメソッド Imshow()は、メソッドが呼ばれるたびに新しいウィンドウが作成されます。

コード例

// 画像の表示
bmp.Imshow("画像");
bmp.Imshow("画像");
bmp.Imshow("画像");

実行結果

 

毎回、ウィンドウを生成しないようにするには、あらかじめNamedWindowsクラスを生成し、Inshowメソッドへ渡してください。

コード例

// ウィンドウの生成
var win = new Mat.NamedWindow("画像");
// 画像の表示
bmp.Imshow(win);
bmp.Imshow(win);
bmp.Imshow(win);

画像の表示機能は、基本的にデバッグ機能です。

より多くの機能を追加したい場合は、FormやPictureBoxを用いて画像を表示してください。

その他

ライブラリファイル(ImagingSolution.Mat.dll)と同じフォルダにXMLファイル(ImagingSolution.Mat.xml)を配置すると、メソッドのヒントが表示されるので、そちらも合わせて参照してください。

 

擬似逆行列を用いた二次式近似の例

擬似逆行列を用いた例はこちらのページ

疑似逆行列(一般逆行列)の計算と使用方法

でExcelを用いて二次式の近似式を解いていますが、これと同じ計算を、この行列演算ライブラリを用いて計算してみたいと思います。

二次式近似は、ざっくり言うと、二次式を

としたとき、式を変形し、

とします。

このとき、xとyのデータを用いて以下のような行列を作成し、

行列部分を記号で表すと、

となるところを、擬似逆行列を用いて、Aの行列を解けば、二次式の未知数を解くことができます。

この処理を行列演算ライブラリを使い、C#プログラムにすると、

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using ImagingSolution;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            // 近似データ
            var dataX = new double[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            var dataY = new double[] { 10, 9, 6, 12, 15, 21, 34, 36, 48, 52 };

            var mat = new double[dataX.Length, 3];

            for (int i = 0; i < dataX.Length; i++)
            {
                mat[i, 0] = dataX[i] * dataX[i];
                mat[i, 1] = dataX[i];
                mat[i, 2] = 1;
            }
            mat.Print();

            // 配列から行列(N行1列)へ変換
            var matY = dataY.ArrayToMat();
            matY.Print();

            // 擬似逆行列で二次式の係数を解く
            var ans = mat.PseudoInverse().Mult(matY);

            // 解の表示
            ans.Print();

            // 近似式を表示
            Console.WriteLine($"y = {ans[0,0]}x^2 + {ans[1, 0]}x + {ans[2, 0]}");

            // キー入力待ち
            Console.WriteLine("続行するには何かキーを押してください . . .");
            Console.ReadKey();
        }
    }
}

となり、実行結果は

 

となります。

最後の計算式の表示は微妙ですが、Excelの結果と比べてみても

見事、C#の結果とExcelの結果が一致することができました!!

【.NET 5 C# WinForms】バージョン情報の設定

プログラムのバージョン情報を指定するのには、.NET Frameworkの時は、プロジェクトのプロパティをクリックし、

表示された画面の アプリケーションアセンブリ情報 をクリックすると

 

アセンブリ情報のウィンドウが表示され、バージョンを設定することができました。

 

しかしながら、.NET 5 で作成したプロジェクト(Windows Forms(.NET Core))ではプロジェクトのプロパティのアプリケーションの画面にアセンブリ情報のボタンがありません。

 

.NET 5 で作成したプロジェクト(Windows Forms(.NET Core))では、プロジェクトのプロパティで表示された画面に パッケージ が追加されているので、このパッケージを選択し、画面の下の方にアセンブリバージョンアセンブリファイルのバージョンがあるので、この値を編集します。

 

 .NET 5 C# Windows Forms プログラミング へ戻る

【.NET 5 C# WinForms】メニューとメニューイベントの追加

(2021.8.3追記)Visual Studio 2019を更新することにより下図のように従来の.NET Frameworkと同等にメニューを追加できるようになりました。本記事は旧バージョン操作方法となります。
下図は Visual Studio Community 2019 バージョン 16.10.4 の画面です。

 


 

.NET5のメニューは、.NET Frameworkのメニューと比べ、メニューの追加方法とイベントの追加方法が変更されています。

今回は、このよう↓にメニューを追加します。

 

メニューの追加

ツールボックスの MenuStrip をフォームへドラッグ&ドロップします。

 

すると、フォームに MenuStrip が追加されますが、.NET Framework のときのように、 ここへ入力 の文字が表示されていません。

(参考).NET FrameworkのMenuStrip

 

.NET 5において、メニューを追加するには、MenuStripプロパティ画面の ItemsCollection を追加するため、右側にある ボタンをクリックします。

 

すると、このようなエディタが表示されます。

 

次に、Addボタンをクリックし、MenuItemを追加します。

.NET5でも、.NET Frameworkと同じように MenuStrip にMenuItem,ComboBox,TextBoxが追加可能です。

 

MenuItemを追加すると、このようになります。

 

さらに後から分かりやすいように、 (Name)の名前を変更し、メニューの表示名(今回は ファイル(&F))を変更しておきます。

 

同様にして ヘルプ 用のメニューも追加した状態がこちら↓

 

さらにメニューの ファイルの下に 開く終了 を追加したいので、今度は DropDownItems の(Collection) の右側にある ボタンをクリックし、 開く終了 を、ファイルの時と同様に追加します。

 

メニューイベントの追加

次に、メニューのクリックイベントを追加したいのですが、.NET Frameworkの時にあった⚡マークがありません。

そこで、プロパティウィンドウの上部にある ▼マーク をクリックしすると、フォームに追加されているコントロール一覧が表示されるので、この中からメニューのイベントを追加したい項目(ここでは menuFileExit)を選択します。

 

すると、 menuFileExitのプロパティが表示され、⚡マークも表示されるので、⚡をクリックし、さらにメニューのクリックイベント(Click)をダブルクリックします。

 

すると、.NET Frameworkと同様に、Clickイベントのコードが生成されるので、イベント内の処理を追記していきます。

 

今回は 終了 のメニューイベントだったので、 this.Close() を追記しました。

 

このようにして、最初に示したようなメニューが追加できます。

 

 .NET 5 C# Windows Forms プログラミング へ戻る

【.NET 5 C# WinForms】新規プロジェクトの作成

.NET5 の Windows Forms のプロジェクトの作成はVisual Studio を起動し、新しいプロジェクトの作成をクリックします。
※.NET 5.0を使用するには Visual Studio 2019 Ver.16.8以降が必要になります。

 

表示されたプロジェクトの種類の中から、 Windows Forms App(.NET) を選択し、次へをクリックします。

 

Windows Forms App(.NET)とは別に Windows フォームアプリケーション(.NET Framework)もあるので、ご注意下さい。(こちらのプロジェクトでは.NET 5 は指定できません。)

また、プロジェクトの種類が多いので、探すのが大変な場合は、上の方にあるプルダウンから C#,Windows ,デスクトップ を選択すると、少し見つけやすくなります。

 

これで、最小限のプロジェクトが作成されますが、C#のソースコードを見ると、従来の.NET Frameworkのソースコードと違いはありません。

ちょっと違うのが、フォームのアイコンぐらいでしょうか??

 

プロジェクトのプロパティで、対象のフレームワーク を確認し、.NET 5.0 を使用する場合は、対象のフレームワークを .NET 5.0 へ変更してください。

 

また、対象のフレームワークを .NET Core から .NET へ変更すると、以下のような警告が表示されます。

この場合、プロジェクトの設定を変更するのですが、変更方法については、下記ページを参照下さい。

【NETSDK1137】Microsoft.NET.Sdk.WindowsDesktop SDKを使用する必要はなくなりました。

 

.NET 5 C# Windows Forms プログラミング へ戻る

.NET5とVisual Studio2019のダウンロード、インストール

2020.11.10(日本時間で11日の1:00)に.NET 5.0 が公開されました。
.NET 5.0 を使用するためには、Visual Studio 2019のVer.16.8以降が必要になるので、それぞれのダウンロードとインストール方法をまとめました。

.NET 5.0 のダウンロード、インストール

.NET 5.0 はマルチプラットフォーム対応なので、LinuxやmacOSにも対応しているのですが、ここではWindows(64bit)を想定しているので、こちら↓のページから、Windowsのx64をダウンロードします。

https://dotnet.microsoft.com/download/dotnet/5.0

インストールそのものは、ダウンロードしたファイル(dotnet-sdk-5.0.100-win-x64.exe)をダブルクリックすれば、インストールできると思います。

 

(2021.11.15追記)

現在は、.NET6が公開されています。
.NET6のダウンロード先はこちら↓

https://dotnet.microsoft.com/download/dotnet/6.0

Visual Studio のダウンロード、インストール

.NET 5.0対応のVisual Studio は Visual Studio 2019 Ver.16.8以降になりますが、こちらから入手できます。

https://visualstudio.microsoft.com/ja/

すでにVisual Studio2019をインストール済みの場合、メニューの ヘルプ→更新プログラムの確認 よりアップデートしてください。

私は個人用途なので、Community版をダウンロードしました。

インストールはダウンロードしたファイル(vs_community__179898993.1589457630.exe)をダブルクリックすると、始まります。

インストールはここ最近のこの画面↓です。

後からも変更できますが、私は

  • .NETデスクトップ開発
  • C++によるデスクトップ開発
  • ユニバーサルWindowsプラットフォーム開発
  • .NET Core クロスプラットフォーム開発

の4つをインストールしました。

 

正式版版のリリースノートについては、こちら↓で確認できます。

https://docs.microsoft.com/ja-jp/visualstudio/releases/2019/release-notes

 

インストールが完了したら、.NET 5.0 の C#のWindows Formsの新規プロジェクトの作成方法をこちら↓にまとめておきましたので、合わせてご参照下さい。

【.NET 5 C# WinForms】新規プロジェクトの作成

 

← .NET 5 C# Windows Forms プログラム へ戻る

【C#】ユーザーコントロールが無くなった?

久々にVisual Studio 2015のC#でユーザーコントロールを作ろうとして、新規プロジェクトを作成しようとしたら、ユーザーコントロールのテンプレートが無い!!

以前はテンプレート→Visual C#→Windowsの中にユーザーコントロールがあったはずなのに。。

 

と思ったら、テンプレート→Visual C#→Windowsの下の階層にクラシックデスクトップというフォルダが作成されていて、この中に Windowsフォームコントロールライブラリ(旧ユーザーコントロール)が移動していました。

 

クラシックデスクトップという表現が、かろうじてレガシーデスクトップとは言っていないだけ、まだマシですが、今後はUWPに移行したいのかな??

 

※この情報はVisual Studio 2015の場合です。

おそらくVisual Studio 2015と2017が同時にインストールされている場合だと思います。

【C#】フーリエ変換(FFT, DFT)プログラム

以前、Excelのマクロを使って、データ個数に応じて高速フーリエ変換(FFT)と離散フーリエ変換(DFT)の処理を自動で切り替えるマクロを作成したのですが、Excelではデータ数が多い時など、使いにくい場合もあるので、今度は、C#でフーリエ変換部分をライブラリ(*.dll)にし、そのライブラリを使ったプログラムを作成しました。

 

実行ブログラムはこちら FourierTransform.zip

ソースコードは、すべてGitHubで公開しています。

https://github.com/ImagingSolution/FourierCSharp

 

使用方法は以下の通りです。

 

プログラムの実行

zipファイルを解凍すると、

FourierTransform.exe

FourierCSharp.dll

sampledata

となっていますので、FourierTransform.exeをダブルクリックして実行してください。

 

フーリエ変換できるデータフォーマット

データは1行あたりに1つのデータのCSVファイルとなります。

例)

1
1.30939551
1.59096344
1.820538079
1.980767038
2.06331351
2.069803727
2.01139008
1.906994542
1.780482109
1.65716389
1.560123288
1.506883103
1.506883103
1.560123288

入力データに実数部と虚数部が含まれる場合は、1行あたりに

実数部、虚数部

とします。

例)

50, 0
0, -25
0, 0
0, -12.5
0, 0
0, 0

サンプルのデータとして、ZIPファイル内の sampledataフォルダ内にいくつかCSVファイルを入れてありますので、そちらを参考にしてください。

 

データの読込

メニューの File → LoadData をクリックし、CSVファイルを指定することで、データが読み込まれ、フーリエ変換の結果(フーリエ変換後の各周波数の大きさ)が表示されます。

 

フーリエ変換はデータの個数が2のn乗個の場合:FFT、その他の場合:DFTを行います。

 

フーリエ変換、逆フーリエ変換の切替

メニューの Fourier direction → Forward もしくは Backward をクリックすることで、離散フーリエ変換、逆離散フーリエ変換が切り替わります。

 

離散フーリエ変換は

$$F(t)=\sum _{ x=0 }^{ N-1 }{ f(x){ e }^{ -i\frac { 2\pi tx }{ N } } } $$

逆離散フーリエ変換は

$$f(t)=\frac { 1 }{ N } \sum _{ t=0 }^{ N-1 }{ F(t){ e }^{ i\frac { 2\pi xt }{ N } } } $$

で計算しています。

 

窓関数

メニューの Window→ Hamming, Hanning, Blackman のいづれかをクリックすることで、それぞれの窓関数を通します。

↓ 窓関数

 

ただし、窓関数を通すと、元に戻せないため、元に戻す場合は、再度、データ読込を行って下さい。

 

免責事項

ここに公開されているプログラムは自由に使って頂いて構いませんが、バグ等による責任は待てませんので、自己責任において参照下さい。

 

フリーウェアへ戻る