今回はPictureBoxの4隅に対して、1番近い座標を検出させるという処理を解説します。
C#で1番近い座標を検出する方法が見つからなかったため、今回記事にしてみました。
・射影補正(台形補正)をする前段階として、自分で4隅をクリックし、各点がどの位置(左上,右上,右下,左下)にあたるか判定させる。
画像処理にて射影補正をする際には良く使われる方法です!
「ココナラ」でC#、OpenCvSharpを使った画像処理アプリの作成を行っています。
もし、作成依頼やお困りごとがあればお気軽にご相談ください!

前準備
「OpenCvSharp4.Windows」と「OpenCvSharp4.Extensions」をインストールします。
インストール方法はこちらの記事の前準備①OpenCvSharp4のインストールをご参照ください。
ソースコード

【出力結果】
1番目:(x:370 y:149)
2番目:(x:192 y:664)
3番目:(x:1276 y:660)
4番目:(x:1152 y:142)
5番目:(x:762 y:379)
左上(x:370 y:149)
右上(x:1152 y:142)
右下(x:1276 y:660)
左下(x:192 y:664)
処理の大まかな流れは以下の通りです。
①画像読み込み
②4隅の定義(読み込み画像の4隅)
③クリックにて対象(本)の4隅を設定(今回は参考として真ん中あたりにも点を付けています)
④読み込み画像の4隅に対して1番近い座標を検出
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using System.IO;
namespace WarpPerspective
{
public partial class Form1 : Form
{
//クリックした4隅を保持
private List<Point2f> _clickPoints;
//読み込んだ画像
private Mat _srcMat;
//pictureBox1の4隅座標
private List<Point2f> _corners;
public Form1()
{
InitializeComponent();
//画像の読み込み
var rootDir = Directory.GetCurrentDirectory();
var fullPath = rootDir + @"\OpenCvSharp4.jpg";
_srcMat = Cv2.ImRead(fullPath);
//PictureBoxのサイズに合わせて表示する
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
pictureBox1.Image = BitmapConverter.ToBitmap(_srcMat);
_clickPoints = new List<Point2f>();
//4隅の値を定義(左上から時計回り)
_corners = new List<Point2f>();
_corners.Add(new Point2f(0, 0));
_corners.Add(new Point2f(_srcMat.Width, 0));
_corners.Add(new Point2f(_srcMat.Width, _srcMat.Height));
_corners.Add(new Point2f(0, _srcMat.Height));
}
private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
//クリックした座標を追加
_clickPoints.Add(new Point2f(e.X * _srcMat.Width / pictureBox1.Width, e.Y * _srcMat.Height / pictureBox1.Height));
//クリックした点を描画
_srcMat.Circle(e.X * _srcMat.Width / pictureBox1.Width, e.Y * _srcMat.Height / pictureBox1.Height, 11, Scalar.Red, -1);
pictureBox1.Image = BitmapConverter.ToBitmap(_srcMat);
}
private void button1_Click(object sender, EventArgs e)
{
if(_clickPoints.Count < 4)
{
MessageBox.Show("ポイントが足りていません!");
return;
}
//4隅に対して1番近いポイントを検出し、代入する
var topLeft = _clickPoints[SerchNearPoint(_corners[0])];
var topRight = _clickPoints[SerchNearPoint(_corners[1])];
var bottomRight = _clickPoints[SerchNearPoint(_corners[2])];
var bottomLeft = _clickPoints[SerchNearPoint(_corners[3])];
int i = 1;
foreach(var point in _clickPoints)
{
Console.WriteLine(i + "番目:" + point);
i++;
}
Console.WriteLine("左上" + topLeft);
Console.WriteLine("右上" + topRight);
Console.WriteLine("右下" + bottomRight);
Console.WriteLine("左下" + bottomLeft);
}
/// <summary>
/// 1番近い座標のインデックスを返す
/// </summary>
/// <param name="corner"></param>
/// <returns></returns>
private int SerchNearPoint(Point2f corner)
{
var coordinateList = new List<double>();
foreach (var clickPoint in _clickPoints)
{
var norm = Math.Sqrt((clickPoint.X - corner.X) * (clickPoint.X - corner.X) + (clickPoint.Y - corner.Y) * (clickPoint.Y - corner.Y));
coordinateList.Add(norm);
}
//1番小さいdistanceListのインデックスを返す
return coordinateList.IndexOf(coordinateList.Min());
}
}
}
①画像の読み、②4隅の定義(読み込み画像の4隅)
①画像の読み込み
今回はプロジェクト内のbin⇒Debugの配下に読み込む画像を入れてあるので、Directory.GetCurrentDirectory()にてカレントディレクトリ(現在の作業フォルダ)を取得し、そのパスにファイル名を追加しています。
②4隅の定義(PictureBox)
読み込んだ画像(_srcMat)の左上⇒右上⇒右下⇒左下の順にList(_corners)に追加します。
public Form1()
{
InitializeComponent();
//画像の読み込み
var rootDir = Directory.GetCurrentDirectory();
var fullPath = rootDir + @"\OpenCvSharp4.jpg";
_srcMat = Cv2.ImRead(fullPath);
//PictureBoxのサイズに合わせて表示する
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
pictureBox1.Image = BitmapConverter.ToBitmap(_srcMat);
_clickPoints = new List<Point2f>();
//4隅の値を定義(左上から時計回り)
_corners = new List<Point2f>();
_corners.Add(new Point2f(0, 0));
_corners.Add(new Point2f(_srcMat.Width, 0));
_corners.Add(new Point2f(_srcMat.Width, _srcMat.Height));
_corners.Add(new Point2f(0, _srcMat.Height));
}
③クリックにて対象(本)の4隅を設定
PictureBoxのイベント「MouseClick」を使用して対象物の4隅を設定します。
PictureBoxと読み込んだ画像はサイズが異なるため、「MouseClick」にて取得したe.X,e.Yに対して補正をする必要があります。
private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
//クリックした座標を追加
_clickPoints.Add(new Point2f(e.X * _srcMat.Width / pictureBox1.Width, e.Y * _srcMat.Height / pictureBox1.Height));
//クリックした点を描画
_srcMat.Circle(e.X * _srcMat.Width / pictureBox1.Width, e.Y * _srcMat.Height / pictureBox1.Height, 11, Scalar.Red, -1);
pictureBox1.Image = BitmapConverter.ToBitmap(_srcMat);
}
④読み込み画像の4隅に対して1番近い座標を検出
読み込んだ画像の4隅に対して、1番近い位置の座標を検出させ、変数に定義していきます。
「SerchNearPoint」という関数を作成し、その中で1番近い座標を計算し、返しています。
1番近い座標を求める方法として、「Math.Sqrt」を使用し、4隅に対しての距離を求めてやります。
coordinateListに計算値を追加し、その中から1番値が小さいものを「IndexOf(coordinateList.Min())」にて検索し、値を設定します。
private void button1_Click(object sender, EventArgs e)
{
if(_clickPoints.Count < 4)
{
MessageBox.Show("ポイントが足りていません!");
return;
}
//4隅に対して1番近いポイントを検出し、代入する
var topLeft = _clickPoints[SerchNearPoint(_corners[0])];
var topRight = _clickPoints[SerchNearPoint(_corners[1])];
var bottomRight = _clickPoints[SerchNearPoint(_corners[2])];
var bottomLeft = _clickPoints[SerchNearPoint(_corners[3])];
int i = 1;
foreach(var point in _clickPoints)
{
Console.WriteLine(i + "番目:" + point);
i++;
}
Console.WriteLine("左上" + topLeft);
Console.WriteLine("右上" + topRight);
Console.WriteLine("右下" + bottomRight);
Console.WriteLine("左下" + bottomLeft);
}
/// <summary>
/// 1番近い座標のインデックスを返す
/// </summary>
/// <param name="corner"></param>
/// <returns></returns>
private int SerchNearPoint(Point2f corner)
{
var coordinateList = new List<double>();
foreach (var clickPoint in _clickPoints)
{
var norm = Math.Sqrt((clickPoint.X - corner.X) * (clickPoint.X - corner.X) + (clickPoint.Y - corner.Y) * (clickPoint.Y - corner.Y));
coordinateList.Add(norm);
}
//1番小さいdistanceListのインデックスを返す
return coordinateList.IndexOf(coordinateList.Min());
}
C#で画像処理を学ぶためのおススメの書籍
C#でOpenCvを扱う方法などを詳しく解説してくれています。
C#で画像処理を解説してくれている本がほとんどない中、こちらの書籍はいろいろなメソッドの使い方等を事例を交えて解説してくれているため、非常に参考になります。
私はこちらの書籍を参考に画像処理を実装しました。
是非参考にしてみてください。
C#の基本が学習したいという方におススメのスクール:侍エンジニア塾のエキスパートコース