19
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【C#】画像にピクセル単位でアクセスする【備忘録】

Last updated at Posted at 2017-01-11

概要

画像を読み込んでヒストグラムを作りたかったのでメモ

画像の読み込み

読み込めるフォーマット

  • gif
  • jpeg
  • bmp
  • exif
  • png
System.Drawing.Bitmap bitmap;
// ローカルファイルの場合
bitmap = new Bitmap("local.png");
// Webの場合
var wc = new System.Net.WebClient();
var stream = wc.OpenRead("http://hogehoge/hoge.png");
bitmap = new Bitmap(stream);
wc.Dispose();
stream.Close();

ピクセルにアクセス

int w = bitmap.Width, h = bitmap.Height;
for (int x = 0; x < w; x++)
{
  for (int y = 0; y < h; y++)
  {
    Color pixel = bitmap.GetPixel(x,y);
    // ARGB
    byte R = pixel.R; // A,G,Bも同様
    // HSB
    float h = pixel.GetHue(),
          s = pixel.GetSaturation(),
          b = pixel.GetBrightness();
    // SetPixelで書き込み
  }
}

ただし写真の様に幅、高さが数千ピクセルある画像だと全部走査するのはかなり時間がかかるので、画像を縮小するか数ピクセル置きに取得するなどしたほうが良さそう?
あとはTask.Runで別スレッドで動作させるか

LockBitsでメモリに展開する

メモリに展開してポインタでアクセスすることで、上の方法よりは速く実行できます。

Bitmap bitmap = ...
PixelFormat pixelFormat = PixelFormat.Format32bppArgb;
int pixelSize = 4;
BitmapData bmpData = bitmap.LockBits(
  new Rectangle(0, 0, bitmap.Width, bitmap.Height),
  ImageLockMode.ReadOnly,  // 書き込むときはReadAndWriteで
  pixelFormat
);
// strideは1次元配列が何個ずつで折り返されているのか その数
// strideがマイナス=ボトムアップ らしい
if (bmpData.Stride < 0) // height * strideで計算するのでマイナスだと困る
{
  bitmap.UnlockBits(bmpData);
  throw new Exception();
}

IntPtr ptr = bmpData.Scan0;
byte[] pixels = new byte[bmpData.Stride * bitmap.Height];
System.Runtime.InteropServices.Marshal.Copy(ptr, pixels, 0, pixels.Length);

for (int y = 0; y < BmpData.Height; y++)
{
  for (int x = 0; x < BmpData.Width; x++)
  {
    //(x,y)のデータ位置
    int pos = y * bmpData.Stride + x * pixelSize;
    // RGB
    byte b = pixels[pos] , g = pixels[pos + 1], r = pixels[pos + 2];
    // 色
    Color col = Color.FromArgb(255, r, g, b);
  }
}
// 変更を(した場合)反映
System.Runtime.InteropServices.Marshal.Copy(pixels,0,ptr,pixels.Length);
bitmap.UnlockBits(bmpData);

300*300の画像を1ピクセルずつ読み込んでみると、大体20~90秒くらいにまで実行時間を短縮できました。なお、unsafeを使えば、ポインタを直接操作することも可能です。(参考文献の方にかかれています)

画像の拡大・縮小

// 300x300に縮小
Bitmap old; // 縮小する元の画像
Bitmap b = new Bitmap(300,300);
Graphics g = Graphics.FromImage(b);
// 縮小・拡大のモード(補間方法)
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.DrawImage(old,0,0,300,300);
// b.Dispose(); g.Dispose();

補完方法

列挙体の名前 補間方法 説明
Bicubic 双三次補間(バイキュービック法) 25%以下の縮小には向いていない
Bilinear 双一次補間(バイリニア法) 50%以下の縮小には向いていない
Default
High 高品質
Low 低品質
HighQualityBicubic 高品質双三次補間
HighQualityBilinear 高品質双一次補間
NearestNeighbor 最近傍補間(ニアレストネイバー法)
Invalid 無効

画像の保存

Bitmap bitmap;
... // bitmapに対する処理
bitmap.Save("save.png");
// フォーマット指定
bitmap.Save("save.png", System.Drawing.Imaging.ImageFormat.Png);

おまけ 図形・線描画

ご指摘があったとおり、Dispose()の呼び忘れなどを考えて、usingで宣言するようにしました。
こうすると自動的にメモリ解放されます。(Disposableなもののみ使えます)

Bitmap bmp;
using(Graphics g = Graphics.FromImage(bmp)){
  // 画像塗りつぶし
  g.FillRectangle(Brushes.White,g.VisibleClipBounds);
  // 幅1pxのペン
  using( Pen p = new Pen(Color.Black,1)){
    // または
    Pens.Black;
    // 線を引く
    g.DrawLine(p,x,y,x2,y2);
    // 長方形
    g.DrawRectangle(p,x,y,w,h);
  }
}

usingはまとめて書けます

using(Graphics g = Graphics.FromImage(bmp))
using(Pen p = new Pen(Color.Black, 1))
{
  // ...
}

参考文献

[プログラムで画像を動的に作成する: .NET Tips: C#, VB.NET]
(http://dobon.net/vb/dotnet/graphics/createimage.html)
@IT:.NET TIPS 画像を読み込むには? - C# Windowsフォーム
[補間方法を指定して画像を拡大、縮小(スケーリング)表示する: .NET Tips: C#, VB.NET]
(http://dobon.net/vb/dotnet/graphics/interpolationmode.html)
LockBitsなどの使い方↓
[色を反転させた画像(ネガティブイメージ)を表示する: .NET Tips: C#, VB.NET]
(http://dobon.net/vb/dotnet/graphics/drawnegativeimage.html#lockbits)

19
34
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?