C#/VB.NETで画像処理⑪<画像のピクセルアクセス>
こんにちは、SKです。
C#/VB.NETで画像処理シリーズの第11弾。
カメラ画像のピクセルにアクセスし、色情報を取り出す方法を紹介します。
PictureBox上にマウスカーソルを合わせると、その座標のR,G,B情報を取得し、
画面上のLabelに表示するプログラムを作っていきます。
OpenCVSharpとは?
(前回)画像のヒストグラム作成
動画手順
①色表示Labelを配置する
フォーム上にLabelを配置します。
見やすいようフォントサイズを大きくしておきます(20pt)。
また、今回はMat画像とPictureBoxのサイズを合わせるため、PictureBoxのDockプロパティをNoneにし、サイズを640×480にします。
②PictureBoxのMouseMoveイベントを作成する
PictureBox上のマウスカーソルの座標取得には、MouseMoveイベントを使用します。
Formのデザイナー画面→プロパティ→"イベント"(⚡アイコン)を選択→MouseMoveの欄をダブルクリックすると、コード上にMouseMoveイベントが生成されます。
↓
マウス座標を格納するPoint型の変数cursorPointを定義し、MouseMoveイベントの第2引数eに格納されている座標X,Yを代入します。
private Point cursorPoint; private void pictureBox1_MouseMove(object sender, MouseEventArgs e) { cursorPoint = new Point(e.X, e.Y); }
③ピクセルの色情報を取得する
Mat画像のピクセルアクセス方法にはいくつか種類があり、OpenCVSharpのWikiに記載があります。
Accessing Pixel · shimat/opencvsharp Wiki · GitHub
↑のWikiには他にも役立つサンプルコードがあります。要チェック!
今回は、最も高速にアクセスできるらしいTypeSpecificMatという方法を採用します。
Indexerの引数はY, Xの順に指定することに注意。
private void Process(Mat img) { //ピクセル情報取得 var mat3 = new Mat<Vec3b>(img); var indexer = mat3.GetIndexer(); Vec3b color = indexer[cursorPoint.Y, cursorPoint.X]; }
Vec3bデータからR,G,Bの値を取り出し、Labelに表示します。
また、その取得した色をLabelの背景色に指定しています。
private void Process(Mat img) { //ピクセル情報取得 var mat3 = new Mat<Vec3b>(img); var indexer = mat3.GetIndexer(); Vec3b pixel = indexer[cursorPoint.Y, cursorPoint.X]; //Labelに表示 this.BeginInvoke((Action)(() => { label1.Text = "R:" + pixel.Item2 + ",G:" + pixel.Item1 + ",B:" + pixel.Item0; label1.BackColor = System.Drawing.Color.FromArgb(pixel.Item2, pixel.Item1, pixel.Item0); })); }
ここでのポイントは、上記コードのようにBeginInvoke経由で文字列を指定すること。
このようにする理由は、Process関数はTask.Runによってサブスレッド処理になっているのに対し、LabelなどのUIに関わる操作はメインスレッドで行わなければならない、というのが.NETの仕様であるからです。
InvokeせずそのままLabelを操作しようとすると、例外が発生します。
C#/VB.NETでは、InvokeやBeginInvokeを使うと、一部の処理をメインスレッド側に戻すことができます。
実行します。
白っぽいところ=151,157,157
赤っぽいところ=107,20,22
最後に、コード全文を載せておきます。
C#
using System; using System.Threading.Tasks; using System.Windows.Forms; using OpenCvSharp; using OpenCvSharp.Extensions; namespace WindowsFormsApp1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { Task.Run(() => { using (VideoCapture v = new VideoCapture(0)) using (Mat img = new Mat()) { while (true) { v.Read(img); Process(img); pictureBox1.Image = BitmapConverter.ToBitmap(img); } } }); } private Point cursorPoint; private void Process(Mat img) { //ピクセル情報取得 var mat3 = new Mat<Vec3b>(img); var indexer = mat3.GetIndexer(); Vec3b pixel = indexer[cursorPoint.Y, cursorPoint.X]; //Labelに表示 this.BeginInvoke((Action)(() => { label1.Text = "R:" + pixel.Item2 + ",G:" + pixel.Item1 + ",B:" + pixel.Item0; label1.BackColor = System.Drawing.Color.FromArgb(pixel.Item2, pixel.Item1, pixel.Item0); })); } private void pictureBox1_MouseMove(object sender, MouseEventArgs e) { cursorPoint = new Point(e.X, e.Y); } } }
VB.NET
何故かTypeSpecificMat (faster)は構文エラーで動かず。
下記はGenericIndexer (reasonably fast)のサンプルです。
Imports OpenCvSharp Imports OpenCvSharp.Extensions Public Class Form1 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load Task.Run(Sub() Using v As New VideoCapture(0) Using img As New Mat Do v.Read(img) Process(img) PictureBox1.Image = img.ToBitmap Loop End Using End Using End Sub) End Sub Private cursorPoint As Point = Nothing Private Sub Process(ByVal img As Mat) 'Dim mat3 As New Mat(Of Vec3b)(img) '何故かエラー 'Dim indexer As MatIndexer(Of Vec3b) = mat3.GetIndexer Dim indexer = img.GetGenericIndexer(Of Vec3b)() Dim pixel As Vec3b = indexer(cursorPoint.Y, cursorPoint.X) Me.BeginInvoke(Sub() Label1.Text = "R:" & pixel.Item2 & ",G:" & pixel.Item1 & ",B:" & pixel.Item0 Label1.BackColor = Color.FromArgb(pixel.Item2, pixel.Item1, pixel.Item0) End Sub) End Sub Private Sub PictureBox1_MouseMove(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseMove cursorPoint = New Point(e.X, e.Y) End Sub End Class