環境差に対応した柔軟な画像比較 (Ranorex + OpenCV連携)

テスト環境の違いにより、人の目では判別しにくい微細な差異が原因で画像検証が失敗するケースがあります。OSSツールであるOpenCVとRanorexを連携し、OpenCVの高度な画像検証アルゴリズムを活用することで、微細な差分を自動的に許容して検証成功と判定できることを確認しました。

今回の記事では、RanorexとOpenCVを組み合わせた画像検証手法についてご紹介します。

OpenCVとは

OpenCV(Open Source Computer Vision Library)は、Intelが開発した画像処理・画像解析・コンピュータビジョン向けのオープンソースライブラリです。

2,500以上の最適化されたアルゴリズムが含まれており、画像の読み込み保存リサイズ回転グレースケール化ノイズ除去などの基本的な処理に加え、画像比較に使われる構造類似度指標SSIM: Structural Similarity Index Measure)などの高度なアルゴリズムも備えています。

SSIMは単純なピクセル差分ではなく、画像の明るさ、コントラスト、構造の類似性を評価し、人間の視覚に近い形で画像の差異を検出することができます。OpenCVはこのように幅広い画像処理機能に加え、高度な画像比較技術も提供しており、幅広い分野で利用されています。

OpenCVの公式サイトは以下です。(2026年8月時点)
https://opencv.org/

RanorexとOpenCVの連携でできること

Ranorexの画像検証機能は、ピクセル単位での厳密な比較で高精度な画像比較ができます。さらに、OpenCVと連携することで、OpenCVの構造類似度指標(SSIM)を用いたより高度な画像比較を利用できるようになります。この連携により、表示環境の違いによる微細な差分を柔軟に許容した画像検証が可能になります。

たとえば、ブラウザーや実行環境の違いにより発生する文字やグラフィックのわずかなズレや揺れを自動検知・調整することで、人間の目では認識できない微細な差分を許容より実用的な判定をおこなうことができます。※画像の複雑さなどで対応できないケースもあります。

RanorexとOpenCVの連携

Ranorexは C# / .NET Framework ベースで動作するツールのため、C#用のオープンソースラッパーである OpenCvSharp を利用します。ここでは、弊社環境で検証した画像比較の設定をご紹介します。※2026年8月時点の情報です。

1.RanorexプロジェクトにOpenCvSarpを追加する

1.Ranorexのプロジェクトで、NuGet パッケージ マネージャーを開く。
メニューバー「プロジェクト」>「パッケージの管理」

2.検索ボックスに「OpenCvSharp」を入力し、OpenCvSharp core libraryOpenCvSharp4 native bindings for Windows x64 をプロジェクトにインストールする。

※今回、ご紹介するユーザーコードでは StronglyTyped.PixelMatch.net のパッケージも利用しますので、追加が必要です。

2.ビルド対象(プラットフォームターゲット)を統一する

1.Ranorexのプロジェクトで、プロジェクトオプションを開く。
メニューバー プロジェクト > パッケージの管理

2.ターゲットCPUをインストールしたOpenCsSharpのビット数に合わせる。上記でインストールしたパッケージは64bitのため、64-bit Intel-compatible processor に設定する。

3.期待画像のスクリーンショットを用意する

今回、ご紹介するユーザーコードでは、手動で取得したスクリーンショットを期待画像として設定できますので、期待画像をローカルマシンに保存ください。また、今回の画像形式はPNGにしています。

4.TMX提供のオートメーションヘルパーの「ReportPageScreenshot」でスクリーンショットの取得アクションを追加する

TMX Automation Helpers リファレンス6:スクリーンショットの出力をご参照ください。
アクションを追加すると以下のような設定ができます。画像ファイルは モジュール名_yyyyMMdd_HHmm_出力画像ファイル名.png の名前で出力されます。

5.OpenCVを利用して、期待画像と実行時に取得した画像を比較するコードを記述する

1.ユーザーコードコレクションをプロジェクトに追加する

追加方法やメソッドの作成についての詳細はソースコードを使用したテスト作成 / ユーザーコードコレクションをご参照ください。

2.作成されたユーザーコードコレクションファイルに使用するパッケージを追加する

using System.IO;
using OpenCvSharp;         
using OpenCvSharp.Quality; 

3.クラスに以下のメソッドを記述する

//取得したスクリーンショットの検索メソッド private static string FindExistingImagePath(string currentImgDirPath, string mdName, string currentImgName, System.DateTime baseTime) { int[] offsets = { -1, 0, 1 }; // ここでoffsetsを定義 foreach (int offset in offsets) { System.DateTime checkTime = baseTime.AddMinutes(offset); string nowTimeStr = checkTime.ToString("yyyyMMdd_HHmm"); string candidateFileName = $"{mdName}_{nowTimeStr}_{currentImgName}.png"; string candidatePath = Path.Combine(currentImgDirPath, candidateFileName); if (File.Exists(candidatePath)) { Report.Info($"存在する画像ファイルを発見: {candidatePath}"); return candidatePath; } } return null; } //OpenCVを利用したSSIMによる画像検証メソッド public static bool CompareImageBySSIM(string baseImgPath, string currentImgDirPath, string currentImgName, double ssimThreshold = 0.95) { var curModule = TestModuleLeaf.Current; string mdName = curModule.Name; System.DateTime datetime = System.DateTime.Now; string currentImgPath = FindExistingImagePath(currentImgDirPath, mdName, currentImgName, datetime); if (currentImgPath == null) { Report.Error("±1分差のファイルが見つかりませんでした。ファイル名やディレクトリを確認してください。"); return false; } using (Mat mat1Gray = Cv2.ImRead(baseImgPath, ImreadModes.Grayscale)) using (Mat mat2Gray = Cv2.ImRead(currentImgPath, ImreadModes.Grayscale)) { if (mat1Gray.Empty() || mat2Gray.Empty()) { Report.Error("画像の読み込みに失敗しました。ファイルパスを確認してください。"); return false; } // 背景(白)ではない「文字が存在する最小の四角枠」を自動検出 Rect textRegion; using (Mat inverted = new Mat()) { Cv2.Threshold(mat1Gray, inverted, 250, 255, ThresholdTypes.BinaryInv); textRegion = Cv2.BoundingRect(inverted); } if (textRegion.Width == 0 || textRegion.Height == 0) { textRegion = new Rect(0, 0, mat1Gray.Width, mat1Gray.Height); } else { textRegion.X = Math.Max(0, textRegion.X - 20); textRegion.Y = Math.Max(0, textRegion.Y - 20); textRegion.Width = Math.Min(mat1Gray.Width - textRegion.X, textRegion.Width + 40); textRegion.Height = Math.Min(mat1Gray.Height - textRegion.Y, textRegion.Height + 40); } // 割り出した「文字エリア」をテンプレート画像として切り出す using (Mat templateMat = new Mat(mat1Gray, textRegion)) using (Mat matchResult = new Mat()) { Cv2.MatchTemplate(mat2Gray, templateMat, matchResult, TemplateMatchModes.CCoeffNormed); Cv2.MinMaxLoc(matchResult, out _, out _, out _, out OpenCvSharp.Point maxLoc); float shiftX = textRegion.X - maxLoc.X; float shiftY = textRegion.Y - maxLoc.Y; Report.Info($"[位置補正自動計算] 検出された文字エリアのズレ -> X方向: {shiftX}px, Y方向: {shiftY}px"); // アフィン変換行列を作成してテスト対象画像を全体シフト using (Mat translationMatrix = new Mat(2, 3, MatType.CV_32FC1)) { translationMatrix.Set<float>(0, 0, 1.0f); translationMatrix.Set<float>(0, 1, 0.0f); translationMatrix.Set<float>(0, 2, shiftX); translationMatrix.Set<float>(1, 0, 0.0f); translationMatrix.Set<float>(1, 1, 1.0f); translationMatrix.Set<float>(1, 2, shiftY); using (Mat resizedMat2AlignedAll = new Mat()) { OpenCvSharp.Size baseSize = mat1Gray.Size(); Cv2.WarpAffine(mat2Gray, resizedMat2AlignedAll, translationMatrix, baseSize); // 文字エリア(ROI)を切り出す using (Mat roiBaseRaw = new Mat(mat1Gray, textRegion)) using (Mat roiTargetAlignedRaw = new Mat(resizedMat2AlignedAll, textRegion)) { // --- 【修正】二値化+モルフォロジー演算でエッジのガタつきを平滑化 --- using (Mat binBase = new Mat()) using (Mat binTarget = new Mat()) using (Mat roiBase = new Mat()) using (Mat roiTargetAligned = new Mat()) { // 固定値(200)で一律二値化し、判定基準のブレを無くす Cv2.Threshold(roiBaseRaw, binBase, 200, 255, ThresholdTypes.Binary); Cv2.Threshold(roiTargetAlignedRaw, binTarget, 200, 255, ThresholdTypes.Binary); // 3x3の構造要素(カーネル)を作成 using (Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(3, 3))) { // クロージング処理(文字のフチの微細なギザギザ・ノイズを埋めて滑らかにする) Cv2.MorphologyEx(binBase, roiBase, MorphTypes.Close, kernel); Cv2.MorphologyEx(binTarget, roiTargetAligned, MorphTypes.Close, kernel); } // 平滑化された画像同士でSSIMを計算 using (var ssimCalc = QualitySSIM.Create(roiBase)) { Scalar score = ssimCalc.Compute(roiTargetAligned); double meanSSIM = score.Val0; Report.Info($"[SSIM判定] エッジ平滑化後のスコア: {meanSSIM:F4} (閾値: {ssimThreshold})"); // 差分画像の保存処理 try { using (Mat diffMat = new Mat()) { Cv2.Absdiff(roiBase, roiTargetAligned, diffMat); using (Mat threshDiff = new Mat()) { Cv2.Threshold(diffMat, threshDiff, 1, 255, ThresholdTypes.Binary); using (Mat highlightMat = new Mat()) { Cv2.CvtColor(roiTargetAlignedRaw, highlightMat, ColorConversionCodes.GRAY2BGR); highlightMat.SetTo(new Scalar(0, 0, 255), threshDiff); string directory = Path.GetDirectoryName(currentImgPath); string filename = Path.GetFileNameWithoutExtension(currentImgPath); string diffImgPath = Path.Combine(directory, filename + "_diff.png"); Cv2.ImWrite(diffImgPath, highlightMat); Report.Info($"差分画像を保存しました: {diffImgPath}"); } } } } catch (Exception ex) { Report.Warn($"差分画像の生成中にエラーが発生しました: {ex.Message}"); } if (meanSSIM >= ssimThreshold) { Report.Success("文字の位置ズレ、滲み、微細なガタつきはすべて許容されました。"); return true; } else { Report.Failure("形状平滑化後もスコアが低いため、文字の欠落やレイアウト崩れが発生しています。"); return false; } } } } } } } } }

記述内容:

  • 文字が存在する領域(最小の四角枠)を自動検出
  • 文字領域をテンプレートして、文字領域の位置ズレを補正
  • 文字領域のクロージング処理で文字の輪郭の微細なギザギザやノイズを滑らかに補正
  • SSIM(構造類似度指数)を用いた画像比較
  • 2つの画像の異なる箇所を赤色でハイライトした差分画像を保存

4.手順3で作成したメソッドを使ったユーザーコードアクションを「ReportPageScreenshot」メソッドのユーザーコードアクションの次に追加する

  • レコーディングモジュールの 新しいアクションの追加 のプルダウンメニューから、ユーザーコード(User code)ライブラリから選択を選択します。
  • 以下の CompareImageBySSIM メソッドを選択します。

アクションを追加すると以下のような設定ができます。閾値は1に近い数値を設定することで、人の目では分からない微差のみを成功とできます。
※0.98以下になると人の目でも認識できる差分が成功となるケースがあります。

検証結果

検証対象の画像

環境の違いにより、全体的な文字ズレと文字の滲み・ギザギザ感が発生している画像を検証対象とします。たとえば、Chromeブラウザーで取得した期待画像を設定しており、テスト実行環境ではEdge Chromiumブラウザーでテストするケースなど。

検証結果レポート

文字の位置ズレや微細なガタつきを自動検出して、画像検証された結果、スコアは1で完全に一致と判定され、画像検証は成功しました。

成功パターン

人の目でも認識できる差分がある場合は、画像検証が失敗します。

失敗パターン

差分画像は出力先(currentImgDirPath)に保存されます。

差分画像

サンプルソリューション

本検証ができるサンプルソリューションは、以下のボタンからダウンロードできます。
※ダウンロードが始まらない場合は、Edgeブラウザーでお試しください。

まとめ

Ranorexの画像検証機能は、ピクセル単位での厳密な比較で高精度な画像比較ができます。さらに、OpenCVと連携することで、OpenCVの構造類似度指標(SSIM)を用いたより高度な画像比較を利用できるようになり、人の目では判別しづらい微細な文字のズレやガタつきを補正し、許容範囲として正確に判定できます。

今回ご紹介したコードのように、お客様ご自身でOpenCVと連携したカスタム画像検証を実装することで、より実用的で柔軟な画像比較が可能になります。ぜひOpenCVの強力なアルゴリズムを活用し、お客様のニーズに合った画像検証環境を構築ください。