垂直又は水平で2色で補完する際、補完する線の色にグラデーションをつけてみました。
OpenCVSharp「インペイント」を試す。その3
前回からの変更点として、フィルターをONにした状態でコンボボックスのフィルターの種類を変更するとフィルターが切り替わるようにしました。水平(垂直)は単色でしたが始点と終点の2色を使うようにしてみました。 その2 ファイル名:Form1.cs...
namespace InpaintSample3;
using System.Diagnostics;
using OpenCvSharp;
using OpenCvSharp.Extensions;
public partial class Form1 : Form
{
static double GetDistance(int x1, int y1, int x2, int y2)
{
int xa = x1 - x2;
int ya = y1 - y2;
return Math.Sqrt(Math.Pow(xa, 2)+Math.Pow(ya, 2));
}
static void DrawGradationLine(ref Mat mat, int x1, int y1, int x2, int y2, ref Scalar c1, ref Scalar c2)
{
int dx = Math.Abs(x2-x1);
int dy = Math.Abs(y2-y1);
int sx = (x1 < x2) ? 1 : -1;
int sy = (y1 < y2) ? 1 : -1;
int err = dx - dy;
double dc = GetDistance(x1, y1, x2, y2);
while(true)
{
double dcc = GetDistance(x1, y1, x2, y2);
double dc1 = dcc / dc;
double dc2 = 1.0 - dc1;
var pic = mat.At<Vec4b>(y1, x1);
pic[0] = (byte)(c1[0] * dc1 + c2[0] * dc2);
pic[1] = (byte)(c1[1] * dc1 + c2[1] * dc2);
pic[2] = (byte)(c1[2] * dc1 + c2[2] * dc2);
pic[3] = 255;
mat.Set<Vec4b>(y1, x1, pic);
if ((x1 == x2) && (y1 == y2)) break;
int e2 = 2 * err;
if (e2 > dy)
{
err = err - dy;
x1 = x1 + sx;
}
if (e2 < dx)
{
err = err + dx;
y1 = y1 + sy;
}
}
}
Mat? view = null;
public class Line_xy
{
public int SY {get; set;} = 0;
public int SX {get; set;} = 0;
public int EY {get; set;} = 0;
public int EX {get; set;} = 0;
};
// 垂直(2色)
static Mat verticalFilterTwoColors(Mat src, Mat mask)
{
Mat result = src.Clone();
var line_xy = new List<Line_xy>();
// 横方向へループ
for (int x = 0; x < src.Width; x++) {
int start_y = -1;
// 縦方向へループ
for (int y = 0; y < src.Height; y++) {
byte b = mask.At<byte>(y, x);
if (b == 255) {
// ON(WHITE)
if (start_y == -1) {
start_y = y; // 開始座標をセット
}
} else {
// OFF(BLACK)
if (start_y > -1) {
var o = new Line_xy { SY = start_y, SX = x, EY = y-1, EX = x};
line_xy.Add(o);
start_y = -1;
}
}
} // for y
if (start_y > -1) {
var o = new Line_xy { SY = start_y, SX = x, EY = result.Height-1, EX = x};
line_xy.Add(o);
}
}// for x
foreach(var line in line_xy) {
if (line.SY > 0) {
var c = result.At<Vec4b>(line.SY-1, line.SX);
//Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
if (line.EY < (result.Height-1))
{
int h = Math.Abs(line.EY - line.SY) / 2;
var c2 = result.At<Vec4b>(line.EY+1, line.EX);
//Cv2.Line(result, line.SX, (line.SY+h), line.EX, line.EY, new Scalar(c2[0], c2[1], c2[2], 255));
var sc1 = new Scalar(c[0], c[1], c[2], 255);
var sc2 = new Scalar(c2[0], c2[1], c2[2], 255);
DrawGradationLine(
ref result, line.SX, line.SY, line.EX, line.EY,
ref sc1,
ref sc2);
}
} else if (line.EY < (result.Height-1)) {
var c = result.At<Vec4b>(line.EY+1, line.EX);
//Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
}
}
return result;
}
// 垂直(単色・上から下へ)
static Mat verticalFilterTop(Mat src, Mat mask)
{
Mat result = src.Clone();
var line_xy = new List<Line_xy>();
// 横方向へループ
for (int x = 0; x < src.Width; x++) {
int start_y = -1;
// 縦方向へループ
for (int y = 0; y < src.Height; y++) {
byte b = mask.At<byte>(y, x);
if (b == 255) {
// ON(WHITE)
if (start_y == -1) {
start_y = y; // 開始座標をセット
}
} else {
// OFF(BLACK)
if (start_y > -1) {
var o = new Line_xy { SY = start_y, SX = x, EY = y-1, EX = x};
line_xy.Add(o);
start_y = -1;
}
}
} // for y
if (start_y > -1) {
var o = new Line_xy { SY = start_y, SX = x, EY = result.Height-1, EX = x};
line_xy.Add(o);
}
}// for x
foreach(var line in line_xy) {
if (line.SY > 0) {
var c = result.At<Vec4b>(line.SY-1, line.SX);
Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
} else if (line.EY < (result.Height-1)) {
var c = result.At<Vec4b>(line.EY+1, line.EX);
Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
}
}
return result;
}
// 垂直(単色・下から上へ)
static Mat verticalFilterBottom(Mat src, Mat mask)
{
Mat result = src.Clone();
var line_xy = new List<Line_xy>();
// 横方向へループ
for (int x = 0; x < src.Width; x++) {
int start_y = -1;
// 縦方向へループ
for (int y = src.Height-1; y >= 0; y--) {
byte b = mask.At<byte>(y, x);
if (b == 255) {
// ON(WHITE)
if (start_y == -1) {
start_y = y; // 開始座標をセット
}
} else {
// OFF(BLACK)
if (start_y > -1) {
var o = new Line_xy { SY = start_y, SX = x, EY = y-1, EX = x};
line_xy.Add(o);
start_y = -1;
}
}
} // for y
if (start_y > -1) {
var o = new Line_xy { SY = start_y, SX = x, EY = result.Height-1, EX = x};
line_xy.Add(o);
}
}// for x
foreach(var line in line_xy) {
if (line.SY < result.Height-1) {
var c = result.At<Vec4b>(line.SY + 1, line.SX);
Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
} else if (line.EY > 0) {
var c = result.At<Vec4b>(line.EY - 1, line.EX);
Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
}
}
return result;
}
// 水平(2色)
static Mat horizontalFilterTwoColors(Mat src, Mat mask)
{
Mat result = src.Clone();
var line_xy = new List<Line_xy>();
// 縦方向へループ
for (int y = 0; y < src.Height; y++) {
int start_x = -1;
// 横方向へループ
for (int x = 0; x < src.Width; x++) {
byte b = mask.At<byte>(y, x);
if (b == 255) {
// ON(WHITE)
if (start_x == -1) {
start_x = x; // 開始座標をセット
}
} else {
// OFF(BLACK)
if (start_x > -1) {
var o = new Line_xy { SY = y, SX = start_x, EY = y, EX = x-1};
line_xy.Add(o);
start_x = -1;
}
}
} // for x
if (start_x > -1) {
var o = new Line_xy { SY = y, SX = start_x, EY = y, EX = result.Width-1};
line_xy.Add(o);
}
}// for y
foreach(var line in line_xy) {
if (line.SX > 0) {
var c = result.At<Vec4b>(line.SY, line.SX-1);
Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
if (line.EX < (result.Width-1))
{
int w = Math.Abs(line.EX - line.SX) / 2;
var c2 = result.At<Vec4b>(line.EY, line.EX+1);
//Cv2.Line(result, (line.SX+w), line.SY, line.EX, line.EY, new Scalar(c2[0], c2[1], c2[2], 255));
var sc1 = new Scalar(c[0], c[1], c[2], 255);
var sc2 = new Scalar(c2[0], c2[1], c2[2], 255);
DrawGradationLine(
ref result, line.SX, line.SY, line.EX, line.EY,
ref sc1,
ref sc2);
}
} else if (line.EX < (result.Width-1)) {
var c = result.At<Vec4b>(line.EY, line.EX+1);
Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
}
}
return result;
}
// 水平(単色・左から右へ)
static Mat horizontalFilterLeft(Mat src, Mat mask)
{
Mat result = src.Clone();
var line_xy = new List<Line_xy>();
// 縦方向へループ
for (int y = 0; y < src.Height; y++) {
int start_x = -1;
// 横方向へループ
for (int x = 0; x < src.Width; x++) {
byte b = mask.At<byte>(y, x);
if (b == 255) {
// ON(WHITE)
if (start_x == -1) {
start_x = x; // 開始座標をセット
}
} else {
// OFF(BLACK)
if (start_x > -1) {
var o = new Line_xy { SY = y, SX = start_x, EY = y, EX = x-1};
line_xy.Add(o);
start_x = -1;
}
}
} // for x
if (start_x > -1) {
var o = new Line_xy { SY = y, SX = start_x, EY = y, EX = result.Width-1};
line_xy.Add(o);
}
}// for y
foreach(var line in line_xy) {
if (line.SX > 0) {
var c = result.At<Vec4b>(line.SY, line.SX-1);
Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
} else if (line.EX < (result.Width-1)) {
var c = result.At<Vec4b>(line.EY, line.EX+1);
Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
}
}
return result;
}
// 水平(単色・右から左へ)
static Mat horizontalFilterRight(Mat src, Mat mask)
{
Mat result = src.Clone();
var line_xy = new List<Line_xy>();
// 縦方向へループ
for (int y = 0; y < src.Height; y++) {
int start_x = -1;
// 横方向へループ
for (int x = src.Width-1; x >= 0; x--) {
byte b = mask.At<byte>(y, x);
if (b == 255) {
// ON(WHITE)
if (start_x == -1) {
start_x = x; // 開始座標をセット
}
} else {
// OFF(BLACK)
if (start_x > -1) {
var o = new Line_xy { SY = y, SX = start_x, EY = y, EX = x-1};
line_xy.Add(o);
start_x = -1;
}
}
} // for x
if (start_x > -1) {
var o = new Line_xy { SY = y, SX = start_x, EY = y, EX = result.Width-1};
line_xy.Add(o);
}
}// for y
foreach(var line in line_xy) {
if (line.SX < result.Width-1) {
var c = result.At<Vec4b>(line.SY, line.SX+1);
Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
} else if (line.EX > 0) {
var c = result.At<Vec4b>(line.EY, line.EX+1);
Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
}
}
return result;
}
static public Bitmap Inpainters(int mode, ref Mat view)
{
using var mask = new Mat();
Cv2.CvtColor(view, mask, ColorConversionCodes.RGB2GRAY);
for (int y = 0; y < view.Height; y++) {
for (int x = 0; x < view.Width; x++) {
var pic = view.At<Vec4b>(y, x);
if (pic[3] == 255) {
mask.Set(y, x, 0);
} else {
mask.Set(y, x, 255);
}
}
}
switch (mode) {
case 0:
using (var tmp = new Mat())
using (var result = new Mat()) {
Cv2.CvtColor(view, tmp, ColorConversionCodes.RGBA2RGB);
Cv2.Inpaint(tmp, mask, result, 0.0, InpaintMethod.Telea);
return BitmapConverter.ToBitmap(result);
}
case 1:
using (var result = verticalFilterTwoColors(view, mask)) {
return BitmapConverter.ToBitmap(result);
}
case 2:
using (var result = verticalFilterTop(view, mask)) {
return BitmapConverter.ToBitmap(result);
}
case 3:
using (var result = verticalFilterBottom(view, mask)) {
return BitmapConverter.ToBitmap(result);
}
case 4:
using (var result = horizontalFilterTwoColors(view, mask)) {
return BitmapConverter.ToBitmap(result);
}
case 5:
using (var result = horizontalFilterLeft(view, mask)) {
return BitmapConverter.ToBitmap(result);
}
case 6:
using (var result = horizontalFilterRight(view, mask)) {
return BitmapConverter.ToBitmap(result);
}
}
return new Bitmap(1, 1);
}
public Form1()
{
InitializeComponent();
Text = "インペイント";
Size = new System.Drawing.Size(850, 800);
var fnt = new Font("MS UI Gothic", 24);
var vb = new Bitmap(800, 600);
using (var g = Graphics.FromImage(vb)) {
g.FillRectangle(Brushes.White, 0, 0, vb.Width, vb.Height);
g.DrawString("こちらにD&D Or Ctrl+Cコピー Ctrl+V貼り付け", fnt, Brushes.Green, 0.0f, 0.0f);
}
var ud = new SplitContainer {
Dock = DockStyle.Fill,
Orientation = Orientation.Horizontal, // 上下
Panel1MinSize = 120,
};
var picboxV = new PictureBox {
SizeMode = PictureBoxSizeMode.Zoom,
Dock = DockStyle.Fill,
AllowDrop = true,
Image = vb,
};
var execBtn = new RadioButton {
Text = "フィルターOFF",
Appearance = Appearance.Button,
AutoCheck = false,
Size = new System.Drawing.Size(180, 50),
};
var flowPanel = new FlowLayoutPanel { Dock = DockStyle.Fill, };
var filterSelect = new List<string>{
"インペイント",
"垂直(2色)","垂直(上)","垂直(下)",
"水平(2色)","水平(左)","水平(右)",};
var filterSelectCmd = new ComboBox {
DataSource = filterSelect.ToArray<string>(),
};
picboxV.DragEnter += (sender, e) => {
if (e.Data == null) return;
if(!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
e.Effect = DragDropEffects.Copy;
};
picboxV.DragDrop += (sender, e) => {
if (e.Data == null) return;
var fd = e.Data.GetData(DataFormats.FileDrop);
if (fd == null) return;
string? path = ((string[])fd)[0];
if (view is not null) view.Dispose();
view = Cv2.ImRead(path, ImreadModes.Unchanged);
if (view.Channels() != 4) {
MessageBox.Show("RGBA以外は非対応", "メッセージ");
view.Dispose();
view = null;
return;
}
if (picboxV.Image is not null) picboxV.Image.Dispose();
picboxV.Image = BitmapConverter.ToBitmap(view);
execBtn.Checked = false;
};
execBtn.Click += async (sender, e) => {
if (view is null) return;
if (execBtn.Checked == true) {
picboxV.Image?.Dispose();
picboxV.Image = BitmapConverter.ToBitmap(view);
execBtn.Text = "フィルターOff";
execBtn.Checked = false;
} else {
int mode = filterSelectCmd.SelectedIndex;
var backCur = this.Cursor;
this.Cursor = Cursors.WaitCursor;
var bitmap = await Task.Run<Bitmap>(()=>Inpainters(mode, ref view));
this.Cursor = backCur;
picboxV.Image?.Dispose();
picboxV.Image = bitmap;
execBtn.Text = "フィルターOn";
execBtn.Checked = true;
}
};
filterSelectCmd.SelectedIndexChanged += async (sender, e) =>
{
if (view is null) return;
if (execBtn.Checked == false) return;
int mode = filterSelectCmd.SelectedIndex;
var backCur = this.Cursor;
this.Cursor = Cursors.WaitCursor;
var bitmap = await Task.Run<Bitmap>(()=>Inpainters(mode, ref view));
this.Cursor = backCur;
picboxV.Image?.Dispose();
picboxV.Image = bitmap;
};
KeyPreview = true;
KeyDown += (sender, e) => {
if (e.KeyData == (Keys.Control | Keys.C)) {
// コピー
if (picboxV.Image is null) return;
var ms = new MemoryStream();
picboxV.Image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
Clipboard.SetData("PNG", ms);
} else if (e.KeyData == (Keys.Control | Keys.V)) {
// 貼り付け
var obj = Clipboard.GetData("PNG");
if (obj == null) return;
var ms = (MemoryStream)obj;
if (ms is null) return;
if (view is not null) view.Dispose();
using(var b = new Bitmap(ms)) {
if (b.PixelFormat != System.Drawing.Imaging.PixelFormat.Format32bppArgb) {
MessageBox.Show("RGBA以外は非対応", "メッセージ");
return;
}
view = BitmapConverter.ToMat(b);
}
if (picboxV.Image is not null) picboxV.Image.Dispose();
picboxV.Image = BitmapConverter.ToBitmap(view);
execBtn.Checked = false;
}
};
ud.Panel1.Controls.Add(picboxV);
flowPanel.Controls.AddRange(new Control[]{
execBtn,filterSelectCmd,});
ud.Panel2.Controls.Add(flowPanel);
Controls.Add(ud);
}
}
インペイント
水平(2色)
コメント