OpenCvSharp4で画像合成

今回はポンコツ2人組がC#のWPFでOpenCvSharp4を使って画像を合成するプログラムに挑戦してみました! 開発環境はVisualStudio2022で、以下のライブラリをNuGetでインストールしています。
・OpenCvSharp4 (バージョン:4.8.0.20230708)
・OpenCvSharp4.runtime.win (バージョン:4.8.0.20230708)
・OpenCvSharp4.Extensions (バージョン:4.8.0.20230708)

C#初心者が制作したものなのでいろいろとおかしい部分が多いと思います。プログラムを流用する際は適宜修正してください。

※この記事は2023/12/21時点の情報です。

MainWindow.xaml
合成後の画像を表示するImageコントロールがGridに挿入されます。

<Window x:Class="Gousei.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="450" Width="800">
    <Grid x:Name="MainGrid">
        <!-- このGridにImageコントロールを追加する -->
    </Grid>
</Window>

MainWindow.xaml.cs
今回は縦横のサイズが異なる3枚の画像ファイルを"F:\sample_image"に配置しています。 プログラムは各画像の上部100pxを切り取った部分を1枚の画像に合成しています。 また、各画像の中で横幅が一番大きいものを合成画像の横幅とし、背景色は黒に設定しています。

今回使用した画像は次の3画像です。

画像1枚目 画像2枚目 画像3枚目

using OpenCvSharp;
using System;
using System.Drawing;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Gousei
{
    public partial class MainWindow : System.Windows.Window
    {
        private System.Windows.Controls.Image imageView;

        public MainWindow()
        {
            InitializeComponent();

            imageView = new System.Windows.Controls.Image();
            MainGrid.Children.Add(imageView);

            try
            {
                Mat[] images = {
                    Cv2.ImRead("F:\\sample_image\\img1.jpg"),
                    Cv2.ImRead("F:\\sample_image\\img2.jpg"),
                    Cv2.ImRead("F:\\sample_image\\img3.jpg")
                };

                // 横幅が一番大きいものを合成画像の横幅とする
                int maxWidth = images.Max(img => img.Width);

                // 合成後の高さを300pxに設定
                int totalCroppedHeight = 300;

                // 合成後のMATを黒い背景で作成
                Mat resultImage = new Mat(new OpenCvSharp.Size(maxWidth, totalCroppedHeight), MatType.CV_8UC3, new Scalar(0, 0, 0));

                int offsetY = 0;
                foreach (Mat image in images)
                {
                    OpenCvSharp.Rect roi = new OpenCvSharp.Rect(0, 0, image.Width, 100);
                    Mat croppedImage = new Mat(image, roi);
                    Mat targetROI = resultImage[new OpenCvSharp.Rect(0, offsetY, croppedImage.Width, croppedImage.Height)];
                    croppedImage.CopyTo(targetROI);
                    offsetY += croppedImage.Height;
                }

                Bitmap bitmap = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(resultImage);
                ImageSource imageSource = BitmapToImageSource(bitmap);
                imageView.Source = imageSource;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                MessageBox.Show($"画像の処理に失敗しました: {ex.Message}");
            }
        }

        private ImageSource BitmapToImageSource(Bitmap bitmap)
        {
            using (var memory = new System.IO.MemoryStream())
            {
                bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp);
                memory.Position = 0;

                BitmapImage bitmapImage = new BitmapImage();
                bitmapImage.BeginInit();
                bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                bitmapImage.StreamSource = memory;
                bitmapImage.EndInit();

                return bitmapImage;
            }
        }
    }
}

以下がアプリケーションを実行した画面です。無事合成した画像が表示されました!

OpenCvSharp4を使って画像を合成

MainWindow.xaml.cs
以下はVConcatメソッドを利用して合成する処理のサンプルコードです。 VConcatメソッドは、与えられたMATを垂直方向に連結するためのもので、MATを上下に配置することができます。 3枚目の画像は上に合成し、残りの画像は下に合成しています。横幅が揃っていないとエラーになるので 不足している分は背景色を黒で最大幅にリサイズしています。

using OpenCvSharp;
using System;
using System.Drawing;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Gousei
{
    public partial class MainWindow : System.Windows.Window
    {
        private System.Windows.Controls.Image imageView;

        public MainWindow()
        {
            InitializeComponent();

            imageView = new System.Windows.Controls.Image();
            MainGrid.Children.Add(imageView);

            try
            {
                Mat[] images = {
                    Cv2.ImRead("F:\\sample_image\\img1.jpg"),
                    Cv2.ImRead("F:\\sample_image\\img2.jpg"),
                    Cv2.ImRead("F:\\sample_image\\img3.jpg")
                };

                int maxWidth = images.Max(img => img.Width);

                // 合成後の高さを300pxに設定
                int totalCroppedHeight = 300;

                // 各画像から上部100pxを切り取り、リストに追加
                var croppedImages = images.Select(img =>
                {
                    OpenCvSharp.Rect roi = new OpenCvSharp.Rect(0, 0, img.Width, 100);
                    return new Mat(img, roi);
                }).ToList();

                // すべての画像を最大幅にリサイズ
                foreach (var croppedImage in croppedImages)
                {
                    if (croppedImage.Width < maxWidth)
                    {
                        Cv2.CopyMakeBorder(croppedImage, croppedImage, 0, 0, 0, maxWidth - croppedImage.Width, BorderTypes.Constant, new Scalar(0, 0, 0));
                    }
                }

                // 最後の画像を上側に、それ以外を下側に配置してMATを合成
                Mat upperImage = croppedImages.Last();
                Mat lowerImages = new Mat();
                foreach (var img in croppedImages.Take(croppedImages.Count - 1))
                {
                    lowerImages.PushBack(img);
                }

                // 上下のMATをVConcatで合成
                Mat resultImage = new Mat();
                Cv2.VConcat(upperImage, lowerImages, resultImage);

                Bitmap bitmap = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(resultImage);
                ImageSource imageSource = BitmapToImageSource(bitmap);
                imageView.Source = imageSource;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                MessageBox.Show($"画像の読み込みに失敗しました: {ex.Message}");
            }
        }

        private ImageSource BitmapToImageSource(Bitmap bitmap)
        {
            using (var memory = new System.IO.MemoryStream())
            {
                bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp);
                memory.Position = 0;

                BitmapImage bitmapImage = new BitmapImage();
                bitmapImage.BeginInit();
                bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                bitmapImage.StreamSource = memory;
                bitmapImage.EndInit();

                return bitmapImage;
            }
        }
    }
}

以下がアプリケーションを実行した画面です。3枚目の画像が上に合成されていますね!

VConcatメソッドを使って画像を合成

管理人情報