はじめに
ふと、画像解析をやってみようと思いました。
OpenCVを使いたかったのですが、事情がありC#のOpenCvSharpで試しました。
画像解析と言えば間違い探しかなと思い、某ファミリーレストランの間違い探しを行ないます。
間違い探し
STEP1. 画像を左右に分ける
画像を切り出す
using (Mat input_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat(input_image, new OpenCvSharp.Rect(0, 0, 950, 963)))
Cv2.ImWrite("保存ファイルパス", output_image);
STEP2. 微調整
左右の画像に微妙にズレがあるので整えます。
⇒右側の画像を左側の画像に合わせます。
特徴点マッチング
using (Mat input_image1 = Cv2.ImRead("左側の画像ファイルパス", ImreadModes.Unchanged))
using (Mat input_image2 = Cv2.ImRead("右側の画像ファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
KeyPoint[] key_point1; //比較元画像の特徴点
KeyPoint[] key_point2; //比較先画像の特徴点
Mat descriptor1 = new Mat(); //比較元画像の特徴量
Mat descriptor2 = new Mat(); //比較先画像の特徴量
List<Point2d> refPoints = new List<Point2d>();
List<Point2d> floatPoints = new List<Point2d>();
DetectAKAZE(input_image1, out key_point1, out descriptor1);
DetectAKAZE(input_image2, out key_point2, out descriptor2);
DescriptorMatcher matcher = DescriptorMatcher.Create("BruteForce");
DMatch[] matches = matcher.Match(descriptor1, descriptor2);
foreach (DMatch v in matches)
{
refPoints.Add(new Point2d(double.Parse(key_point1[v.QueryIdx].Pt.X.ToString()), double.Parse(key_point1[v.QueryIdx].Pt.Y.ToString())));
floatPoints.Add(new Point2d(double.Parse(key_point2[v.TrainIdx].Pt.X.ToString()), double.Parse(key_point2[v.TrainIdx].Pt.Y.ToString())));
}
Mat homo = Cv2.FindHomography(InputArray.Create(floatPoints), InputArray.Create(refPoints), HomographyMethods.Ransac);
Cv2.WarpPerspective(input_image2, output_image, homo, mat.Size());
Cv2.ImWrite("保存ファイルパス", output_image);
}
STEP3. 差分取得
2つの画像の差分を取ります。
画像比較
using (Mat input_image1 = Cv2.ImRead("左側の画像ファイルパス", ImreadModes.Unchanged))
using (Mat input_image2 = Cv2.ImRead("右側の画像ファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
Cv2.Absdiff(input_image1, input_image2, output_image);
Cv2.ImWrite("保存ファイルパス", output_image);
}
STEP4. ノイズ除去
微妙なズレによる差分が出るので除去します。
これを
↓
こう
ノイズ除去
using (Mat input_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
Cv2.MedianBlur(input_image, output_image, 5);
Cv2.ImWrite("保存ファイルパス", output_image);
}
STEP5. 二極化(色検知)
わずかに残っているノイズを除去して、モノクロにします。
これを
↓
こう
二極化
using (Mat input_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Grayscale))
using (Mat output_image = new Mat())
{
Cv2.Threshold(input_image, output_image, 50, 255, ThresholdTypes.Binary);
Cv2.ImWrite("保存ファイルパス", output_image);
}
二極化によって除去したかったのですが、左下の差分も消えてしまいました。
そこで、色検出を使いました。
色検知
using (Mat input_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
Scalar s_min = new Scalar(0, 0, 0);
Scalar s_max = new Scalar(50, 50, 50);
Cv2.InRange(input_image, s_min, s_max, output_image);
Cv2.ImWrite("保存ファイルパス", output_image);
}
STEP6. 淵の差分除去
STEP7. 差分範囲の膨張
差分の付近を求めたいので差分範囲を膨張させます。
これを
↓
こう
膨張
using (Mat input_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
Cv2.Dilate(input_image, output_image, new Mat(new OpenCvSharp.Size(21, 21), MatType.CV_8UC1));
Cv2.ImWrite("保存ファイルパス", output_image);
}
STEP8. 差分範囲を縁取り
輪郭取得
using (Mat neiborhood8 = new Mat(new OpenCvSharp.Size(3, 3), MatType.CV_8U))
using (Mat gray_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Grayscale))
using (Mat dilate_image = new Mat())
using (Mat diff_image = new Mat())
using (Mat output_image = new Mat())
{
for (int y = 0; y < 3; y++)
for (int x = 0; x < 3; x++)
neiborhood8.Set<byte>(y, x, 1);
Cv2.Dilate(gray_image, dilate_image, neiborhood8, null, 1);
Cv2.Absdiff(gray_image, dilate_image, diff_image);
Cv2.BitwiseNot(diff_image, output_image);
Cv2.ImWrite("保存ファイルパス", output_image);
}
STEP9. 淵の膨張
膨張
using (Mat input_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
Cv2.Dilate(input_image, output_image, new Mat(new OpenCvSharp.Size(21, 21), MatType.CV_8UC1));
Cv2.ImWrite("保存ファイルパス", output_image);
}
STEP10. 色変換
using (Mat input_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Unchanged))
{
for (int i = 0; i < input_image.Width; i++)
for (int j = 0; j < input_image.Height; j++)
{
Vec3b pix = mat.Get<Vec3b>(j, i);
pix[0] = (byte)0;
pix[1] = (byte)0;
mat.Set<Vec3b>(j, i, pix);
}
Cv2.ImWrite("保存ファイルパス", input_image);
}
STEP11. 色の反転
using (Mat input_image = Cv2.ImRead("読み込みファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
Cv2.BitwiseNot(input_image, output_image);
Cv2.ImWrite("保存ファイルパス", output_image);
}
STEP12. 元画像に差分を描画
2段階で作成します。
using (Mat input_image = Cv2.ImRead("STEP1の画像ファイルパス", ImreadModes.Unchanged))
using (Mat diff_image = Cv2.ImRead("STEP11の画像ファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
Cv2.BitwiseAnd(input_image, diff_image, output_image);
Cv2.ImWrite("保存ファイルパス", output_image);
}
using (Mat input_image = Cv2.ImRead("上記の画像のファイルパス", ImreadModes.Unchanged))
using (Mat diff_image = Cv2.ImRead("STEP10の画像ファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
Cv2.BitwiseOr(input_image, diff_image, output_image);
Cv2.ImWrite("保存ファイルパス", output_image);
}
STEP13. 画像を結合
using (Mat input_image1 = Cv2.ImRead("左側の画像ファイルパス", ImreadModes.Unchanged))
using (Mat input_image2 = Cv2.ImRead("右側の画像ファイルパス", ImreadModes.Unchanged))
using (Mat output_image = new Mat())
{
Cv2.HConcat(input_image1, input_image2, output_image);
Cv2.ImWrite("保存ファイルパス", output_image);
}
おわりに
デジタル画像であれば単純に比較すれば差分が取れます。
※STEP3の時点でも目視で確認が出来ます。
次の段階として、写真から差分を取ってみます。