C# だけで画像のグレースケール化の処理

Bitmap クラスには、GetPixel 、SetPixel というメソッドがあるので、それを使って一ピクセルずつ処理してみた。
が!!!

これがまたクソ遅い。半端じゃない。

なので、バイト列を操作して画像を操作する方法を検索して、出来たのでメモ。
ちなみに、ピクセル操作と、バイト列操作では、100 倍くらい処理時間が違う。
SetPixel、GetPixel を使って処理すると、9000ms かかるが、
バイト列を取り出して操作すると、90ms で終わる、といった具合。

int 型とかビットシフトとかで演算してるけど、浮動小数点の演算が得意な CPU があるとか無いとかで、下手に int でやるより float でやったほうが早いとの話もあるが、試してないのでよく分からない。

using System;
using System.Drawing; // 参照設定に System.Drawing を追加する必要アリ
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
// 画面を用意
Bitmap bmp = Bitmap.FromFile("hoge.jpg");

// 画像データをメモリ上に固定?
BitmapData bmpdata = bmp.LockBits(
    new Rectangle(0, 0, bmp.Width, bmp.Height),
    ImageLockMode.ReadWrite,
    PixelFormat.Format32bppArgb
);

// グレイスケール用のアレ
int rp = (int)(0.298912 * 1024);
int gp = (int)(0.586611 * 1024);
int bp = (int)(0.114478 * 1024);

// バイト配列にコピー
byte[] ba = new byte[bmp.Width * bmp.Height * 4];
Marshal.Copy(bmpdata.Scan0, ba, 0, ba.Length);

// 処理
int pixsize = bmp.Width * bmp.Height * 4;
for (int i = 0; i < pixsize; i += 4)
{
    // 画像のバイト列って、ARGB じゃなくて、BGRAになってるっぽい??
    // リトルエンディアンか。
    byte g = (byte)((bp * ba[i + 0] + gp * ba[i + 1] + rp * ba[i + 2]) >> 10);
    ba[i + 0] = g;      // ブルー
    ba[i + 1] = g;      // グリーン
    ba[i + 2] = g;      // レッド
    ba[i + 3] = 0xFF;   // アルファ
}

// 元のところに書き込む。
Marshal.Copy(ba, 0, bmpdata.Scan0, ba.Length);

bmp.UnlockBits(bmpdata);

こうして出来た Bitmap を表示したり保存すると、グレイスケール化できている事を確認出来る。

もし、もっと複雑な画像処理や、画像認識を行いたいという場合は、OpenCV を使うと良さそう。

schima.hatenablog.com – OpenCvSharpをつかう

C# で画像の中に一致するパターンがあるか探す

C# のネットワーク通信ソケットのタイムアウト設定

ソケットに対して、タイムアウト設定を行う。

// 接続待ち
Socket client = tcplistener.AcceptSocket();

// 2秒データを受信しなかったらタイムアウトになるように設定
client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 2000);

このコードでタイムアウトした場合

System.IO.IOException: 転送接続からデータを読み取れません。 ---> 
System.Net.Sockets.SocketException: 接続済みの呼び出し先が一定の時間を過ぎても正しく応答しなかったため、接続できませんでした。
または接続済みのホストが応答しなかったため、確立された接続は失敗しました。

という内容の Exception が投げられる。

TcpClient でタイムアウト設定を行うなら

TcpClient client = new TcpClient("127.0.0.1", 12345);
client.ReceiveTimeout = 2000;
client.SendTimeout = 2000;

こんな感じで出来る。

送信タイムアウト(データを通信相手に一定時間送れなかった、受け取ってもらえなかった)
受信タイムアウト(通信相手から一定時間、データが届かなかった)
接続タイムアウト(通信相手に、ネットワーク的に接続できなかった、IPアドレス・ホスト名が間違っている、ポート番号が違う、相手のファイアウォールに阻まれた、など)

といった理由でタイムアウトが発生する事がある。

C# の TcpListener で接続してきたクライアントのIPアドレスを取得

// TcpListener で接続をうけ、Socket として取る。
Socket client = tcplistener.AcceptSocket();

// エンドポイントとかいうのをもらう
IPEndPoint endpoint = (IPEndPoint)client.RemoteEndPoint;

// そこから接続している相手の IPAddress が取れる。
IPAddress address = endpoint.Address;

// NetworkStream を作成。
NetworkStream stream = new NetworkStream(client);

C# で 2ギガを超えるファイルを StreamWriter で書き込むと例外が投げられる。

以下のようなコードを実行すると、ファイルサイズが 2GB を超えた時点で例外が投げられる。

Encoding enc = Encoding.GetEncoding(932);
string str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\r\n";

string filepath = Path.GetTempFileName();
long count = 0;
try
{
    // ファイルを新規作成
    using (StreamWriter writer = new StreamWriter(filepath, false, enc))
    {
        while (true)
        {
            writer.Write(str);

            if (count++ > 100000)
            {
                count = 0;
                Console.Write("{0:#,0} KB\r", writer.BaseStream.Position / 1024);
            }
        }
    }
}
catch (Exception ex)
{
    Console.WriteLine();
    Console.WriteLine(ex);
}
finally
{
    try
    {
        File.Delete(filepath);
    }
    catch { }
}

例外は、こんな感じ。

System.ArgumentException: GetBytes() または GetByteCount() を呼び出す前に、Convert() 操作を完了するか、または Encoder.Reset() を呼び出さなければなりません。エンコーダ '日本語 (シフト JIS)' フォールバック 'System.Text.InternalEncoderBestFitFallback'。
 場所 System.Text.DBCSCodePageEncoding.GetBytes(Char* chars, Int32 charCount, Byte* bytes, Int32 byteCount, EncoderNLS encoder)
 場所 System.Text.EncoderNLS.GetBytes(Char* chars, Int32 charCount, Byte* bytes, Int32 byteCount, Boolean flush)
 場所 System.Text.EncoderNLS.GetBytes(Char[] chars, Int32 charIndex, Int32 charCount, Byte[] bytes, Int32 byteIndex, Boolean flush)
 場所 System.IO.StreamWriter.Flush(Boolean flushStream, Boolean flushEncoder)
 場所 System.IO.StreamWriter.Dispose(Boolean disposing)
 場所 System.IO.TextWriter.Dispose()

この2ギガ問題を解決するには・・・ファイルサイズが2ギガを越える前に、一度ストリームを閉じて、その後、ファイルに追記する形でストリームを開きなおすと良い。
具体的なコードは以下の通り。

Encoding enc = Encoding.GetEncoding(932);
string str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\r\n";

string filepath = Path.GetTempFileName();
long count = 0;

long cycle = 1 * 1024 * 1024 * 1024; // 1G
long streamsize = cycle;

// ファイルを新規作成
StreamWriter writer = new StreamWriter(filepath, false, enc);
try
{
    while (true)
    {
        writer.Write(str);

        if (count++ > 100000)
        {
            count = 0;
            Console.Write("{0:#,0} KB\r", writer.BaseStream.Position / 1024);
        }

        if (writer.BaseStream.Position > streamsize)
        {
            streamsize += cycle;
            writer.Flush();
            writer.Close();
            // ファイルに追記する
            writer = new StreamWriter(filepath, true, enc);
        }
    }
}
catch (Exception ex)
{
    Console.WriteLine();
    Console.WriteLine(ex);
}
finally
{
    try
    {
        writer.Close();
    }
    catch { }
    try
    {
        File.Delete(filepath);
    }
    catch { }
}

こんな感じ。
ここでは、1ギガごとに、ストリームを作り直すようにした。
ちなみに、using を使うと、変数が読み取り専用になり StreamWriter を作り直せないので外した。
そのため、 finally で閉じ忘れないようにした。

C# で埋め込んだリソースからデータを読み取る

プロジェクトを右クリックし、追加、既存の項目
そして、ファイルの種類をすべてのファイル、にして、アセンブリファイルに埋め込みたいファイルを選択。
プロジェクトに追加されただけだと何もしないので、追加されたファイルを選択し、ビルドアクションプロパティを、埋め込まれたりソースにする。

これで、ビルドして出来た実行ファイル、DLL にファイルが埋め込まれる。
実行ファイル、DLLに埋め込んだファイルは、C# のコード上から読み込みが行える。

using System;
using System.IO;
using System.Reflection;

public static Stream GetAssemblyStream(string name)
{
    Stream stream;

    // アセンブリから読み込む努力。
    Assembly asm = Assembly.GetExecutingAssembly();
    //Assembly asm = Assembly.GetAssembly(typeof(Program)); // 自分のクラスを指定

    // 何もせずにアセンブリから取れたなら返す。
    stream = asm.GetManifestResourceStream(name);
    if (stream != null)
    {
        return stream;
    }

    // パスとかいじってアセンブリから取ろうとする。
    string fullname = asm.FullName;
    string[] asmname = fullname.Split(",".ToCharArray());

    if (asmname.Length >= 2)
    {
        name = name.Replace("/", ".").Replace("\\", ".");
        name = asmname[0] + "." + name;
        stream = asm.GetManifestResourceStream(name);

        return stream;
    }

    // どれもダメだったら null
    return null;
}

例えば、このメソッドを使って、画像のリソースを埋め込んだ場合は、次のようなコードで Bitmap クラスとして扱える。

Bitmap bmp = Bitmap.FromStream(GetAssemblyStream("umekomareta.jpg"))

PowerShell で複数のホストにPingを打ち続ける

1つのホストだけなら

ping -t hostname

みたいな感じで延々と ping を打てるが、ping を打ちたいマシンが複数ある場合はちょっとむずかしい。
なので、PowerShell でやってみた。

$l=@("host1","host2","host3");
while(1){
    $r=$(get-date).DateTime.ToString();
    $r+="`n";
    foreach($a in $l){
        $r+=ping $a -n 1|select-string -Pattern "Reply|Request";
        $r+="`t$a`n"
    };
    $r+="----";
    $r;
    Start-sleep -s 1;
    $n=ipconfig /flushdns
}

host1, host2, host3 の所に、ホスト名か IP を入れていく。必要に応じて増やす。

実行すると

2012年8月3日 14:06:06
Reply from 172.16.1.1: bytes=32 time&lt;1ms TTL=64
Reply from 172.16.1.2: bytes=32 time&lt;1ms TTL=64
Reply from 172.16.1.3: bytes=32 time&lt;1ms TTL=64

こんな感じで、ずっと Ping が実行され続ける。