やりたいこと
背景画像と熨斗の蝶画像(背景非透過)を重ね合わせて、背景付の熨斗画像を作成していきます。
透過済みの蝶画像を使用すれば、透過処理を行う必要がないがすべての蝶画像が背景透過済みとは限らないため、背景が非透過であることを前提として実装します。
※背景透過の際、どの部分が背景か判断するため輪郭を取得し、輪郭以外を透過する必要があります。

実装方法
- 熨斗画像の輪郭を取得
- 熨斗画像の背景の透過
- 熨斗画像と背景を重ね合わせる
輪郭の取得、背景の透過にはOpenCvSharpを使用していきます。
コードの解説
熨斗画像の輪郭を取得
using System.Drawing;
using System.Drawing.Imaging;
using OpenCvSharp;
using OpenCvSharp.Extensions;
public Mat OutlineExtraction(Bitmap bitmap)
{
// 画像の読み込み
var image = BitmapConverter.ToMat(bitmap);
// グレースケール
var grayMat = image.CvtColor(ColorConversionCodes.BGR2GRAY);
// 2値化
var thresholdImege = grayMat.Threshold(230, 255, ThresholdTypes.BinaryInv);
// ジャグ配列
OpenCvSharp.Point[][] contours;
OpenCvSharp.HierarchyIndex[] hierarchyIndex;
// 輪郭抽出
thresholdImege.FindContours(out contours, out hierarchyIndex, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple);
// 輪郭マスクを作成
var contourMask = new Mat(image.Rows, image.Cols, MatType.CV_8UC1, Scalar.Black);
Cv2.DrawContours(contourMask, contours, -1, Scalar.White, -1, hierarchy: hierarchyIndex);
return contourMask;
}
輪郭抽出を行いたい画像のBitmapを受け取って、輪郭マスクのMatを返すコードです。
他のコードから呼び出す都合上、入力をBitmap形式にしていますが、輪郭抽出のみを行いたい場合は必要に応じて、画像の読み込み部分を変更してあげてください。
熨斗画像の背景の透過
using System.Drawing;
using System.Drawing.Imaging;
using OpenCvSharp;
using OpenCvSharp.Extensions;
public Bitmap TransparentExecute(string imagePath)
{
// 画像の読み込み
var image = Cv2.ImRead(imagePath, ImreadModes.Unchanged);
// アルファチャンネルを持つMatを作成
var transparentImage = new Mat(image.Rows, image.Cols, MatType.CV_8UC4);
// アルファチャンネルを0で初期化
transparentImage.SetTo(Scalar.All(0));
// 4チャンネルに変換
Cv2.CvtColor(image, image, ColorConversionCodes.BGR2BGRA);
// マスクを取得
var contourMask = OutlineExtraction(new Bitmap(imagePath));
// アルファチャンネルにマスクをコピー
var alphaMask = new Mat(image.Rows, image.Cols, MatType.CV_8UC1, Scalar.Black);
contourMask.CopyTo(alphaMask);
// 透過処理後のMatを作成
var outputImage = new Mat();
var channels = new Mat[] { new Mat(image.Rows, image.Cols, MatType.CV_8UC3), alphaMask };
Cv2.Merge(channels, transparentImage);
// 背景透過処理
image.CopyTo(outputImage, contourMask);
outputImage.CopyTo(transparentImage.SubMat(0, image.Rows, 0, image.Cols), alphaMask);
return OpenCvSharp.Extensions.BitmapConverter.ToBitmap(transparentImage);
}
輪郭の情報を取得し、背景を透明にするコードです。
透明な画像と、輪郭抽出を行ったマスクの画像を使用して、背景の透過処理を行います。
熨斗画像と背景を重ね合わせる
using System.Drawing;
using System.Drawing.Imaging;
public Bitmap MergeImages(Bitmap bottomImage, Bitmap topImage)
{
var margedImage = new Bitmap(bottomImage.Width, bottomImage.Height);
// 画像サイズを取得
var width = topImage.Width;
var height = topImage.Height;
// 上の画像を中心に配置するための座標を取得
var x = (bottomImage.Width - width) / 2;
var y = (bottomImage.Height - height) / 2;
using (Graphics graphics = Graphics.FromImage(margedImage))
{
graphics.DrawImage(bottomImage, 0, 0);
graphics.DrawImage(topImage, x, y, width, height);
}
return margedImage;
}
背景画像と、透過処理を行った蝶画像を重ね合わせるコードです。
画像のずれがおこらないよう、中心座標を指定して重ね合わせています。
完成したコード
1~3の処理を順番に実装したコードになります。
背景画像と熨斗画像のサイズが全く同じなことは稀だと思うので、画像を重ね合わせる前にサイズの調整処理を入れています。
using System.Drawing;
using System.IO;
namespace TransparentBackground
{
internal class Program
{
static void Main(string[] args)
{
var bl = new BusinessLogic();
var backgroundImagePath = "背景画像.png";
var noshiImagePath = "のし画像.png";
// 熨斗画像の背景を透過する
var noshiImage = bl.TransparentExecute(noshiImagePath);
// 背景画像
var backgroundImage = new Bitmap(backgroundImagePath);
/*-画像サイズを変更する------------*/
var outputHeight = 2894;
var outputWidth = 4093;
noshiImage = bl.ResizeImage(noshiImage, outputHeight, outputWidth);
backgroundImage = bl.ResizeImage(backgroundImage, outputHeight, outputWidth);
/*-------------------------------*/
// 画像を重ね合わせる
var resultImage = bl.MergeImages(backgroundImage, noshiImage);
// 結果画像を出力する
var outputDir = "出力先フォルダパス";
var resultFileName = "resultImage.png";
var resultFilePath = Path.Combine(outputDir, resultFileName);
resultImage.Save(resultFilePath);
}
}
}
using System.Drawing;
using System.Drawing.Imaging;
using OpenCvSharp;
using OpenCvSharp.Extensions;
namespace TransparentBackground
{
public class BusinessLogic
{
/// <summary>
/// 透過処理
/// </summary>
/// <param name="imagePath"></param>
/// <returns></returns>
public Bitmap TransparentExecute(string imagePath)
{
// 画像の読み込み
var image = Cv2.ImRead(imagePath, ImreadModes.Unchanged);
// アルファチャンネルを持つMatを作成
var transparentImage = new Mat(image.Rows, image.Cols, MatType.CV_8UC4);
// アルファチャンネルを0で初期化
transparentImage.SetTo(Scalar.All(0));
// 4チャンネルに変換
Cv2.CvtColor(image, image, ColorConversionCodes.BGR2BGRA);
// マスクを取得
var contourMask = OutlineExtraction(new Bitmap(imagePath));
// アルファチャンネルにマスクをコピー
var alphaMask = new Mat(image.Rows, image.Cols, MatType.CV_8UC1, Scalar.Black);
contourMask.CopyTo(alphaMask);
// 透過処理後のMatを作成
var outputImage = new Mat();
var channels = new Mat[] { new Mat(image.Rows, image.Cols, MatType.CV_8UC3), alphaMask };
Cv2.Merge(channels, transparentImage);
// 背景透過処理
image.CopyTo(outputImage, contourMask);
outputImage.CopyTo(transparentImage.SubMat(0, image.Rows, 0, image.Cols), alphaMask);
return OpenCvSharp.Extensions.BitmapConverter.ToBitmap(transparentImage);
}
/// <summary>
/// 輪郭のマスクを取得する
/// </summary>
/// <param name="bitmap"></param>
/// <returns></returns>
public Mat OutlineExtraction(Bitmap bitmap)
{
// 画像の読み込み
var image = BitmapConverter.ToMat(bitmap);
// グレースケール
var grayMat = image.CvtColor(ColorConversionCodes.BGR2GRAY);
// 2値化
var thresholdImege = grayMat.Threshold(230, 255, ThresholdTypes.BinaryInv);
// ジャグ配列
OpenCvSharp.Point[][] contours;
OpenCvSharp.HierarchyIndex[] hierarchyIndex;
// 輪郭抽出
thresholdImege.FindContours(out contours, out hierarchyIndex, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple);
// 輪郭マスクを作成
var contourMask = new Mat(image.Rows, image.Cols, MatType.CV_8UC1, Scalar.Black);
Cv2.DrawContours(contourMask, contours, -1, Scalar.White, -1, hierarchy: hierarchyIndex);
return contourMask;
}
/// <summary>
/// 背景画像とマージする
/// </summary>
/// <param name="bottomImage"></param>
/// <param name="topImage"></param>
/// <returns></returns>
public Bitmap MergeImages(Bitmap bottomImage, Bitmap topImage)
{
var margedImage = new Bitmap(bottomImage.Width, bottomImage.Height);
// 画像サイズを取得
var width = topImage.Width;
var height = topImage.Height;
// 上の画像を中心に配置するための座標を取得
var x = (bottomImage.Width - width) / 2;
var y = (bottomImage.Height - height) / 2;
using (Graphics graphics = Graphics.FromImage(margedImage))
{
graphics.DrawImage(bottomImage, 0, 0);
graphics.DrawImage(topImage, x, y, width, height);
}
return margedImage;
}
/// <summary>
/// 画像サイズを変更する
/// </summary>
/// <param name="sourceImage"></param>
/// <param name="targetHeight"></param>
/// <param name="targetWidth"></param>
/// <returns></returns>
public Bitmap ResizeImage(Bitmap sourceImage, int targetHeight, int targetWidth)
{
// リサイズ後の幅・高さを計算
var width = sourceImage.Width;
var height = sourceImage.Height;
float aspectRatio = (float)width / height;
if (aspectRatio < 1)
{
// 幅が高さより大きい場合
width = targetWidth;
height = (int)(width / aspectRatio);
}
else
{
// 高さが幅より大きい場合
height = targetHeight;
width = (int)(height * aspectRatio);
}
// リサイズ後の画像を作成
Bitmap resultImage = new Bitmap(width, height, PixelFormat.Format32bppArgb);
// リサイズ処理
using (Graphics graphics = Graphics.FromImage(resultImage))
{
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.DrawImage(sourceImage, 0, 0, width, height);
}
return resultImage;
}
}
}
コメント