C#からOpenCVで顔を検出して"ゆっくり"してみよう
なにやら↓こういうのが流行っていたようですね。
元ネタ
Perlでアニメ顔を検出&解析するImager::AnimeFace
Yukkurizer - アニメキャラゆっくり化CGI
ゆっくりしてみよう
というわけで、やつけで適当にC#でゆっくりしてみました。
C#やVB.NET、あるいはIronPythonなどでOpenCVを簡単に扱えるクロスプラットホーム.NETラッパーである*2、
Emgu CVというオープンソースのライブラリを使って顔を検出しています。
今回はOpenCVをインストールすることでデフォルト提供されている顔検出のhaarcascade(XMLファイル)を利用していますが、
アニメ顔を認識するように調教した学習させたhaarcascadeを使えば、元ネタと同等のことができるでしょう。
たいした内容ではありませんが、以下C#のサンプルコードです。
using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Windows.Forms; using Emgu.CV; using Emgu.CV.Structure; namespace WindowsFormsApplication1 { public partial class Form1 : Form { private Image<Bgr, Byte> _oldimg = null; private Image<Bgr, Byte> _img = null; private readonly Bitmap _yukkuri = new Bitmap(Path.GetDirectoryName(Application.ExecutablePath) + @"\yukkuri.bmp"); private const string xmlpath = @"C:\Program Files\OpenCV\haarcascade\"; public Form1() { InitializeComponent(); Init(); } private void Init() { _yukkuri.MakeTransparent(Color.White); this.panel1.AutoScroll = true; this.pictureBox1.SizeMode = PictureBoxSizeMode.AutoSize; this.cmbHaarcascade.DropDownStyle = ComboBoxStyle.DropDownList; foreach (var item in GetHaarCascade()) { this.cmbHaarcascade.Items.Add(new ComboItem(new HaarCascade(xmlpath + item + ".xml"), item)); } this.cmbHaarcascade.SelectedIndex = 0; } private IEnumerable<string> GetHaarCascade() { yield return "haarcascade_frontalface_alt2"; yield return "haarcascade_frontalface_alt"; yield return "haarcascade_frontalface_alt_tree"; yield return "haarcascade_frontalface_default"; yield return "haarcascade_fullbody"; yield return "haarcascade_lowerbody"; yield return "haarcascade_profileface"; yield return "haarcascade_upperbody"; } private void btnYukkuri_Click(object sender, EventArgs e) { if (_img == null) return; //モノクロ変換 var imgGray = _img.Convert<Gray, Byte>(); Bitmap img1 = _img.Bitmap; var hc = (ComboItem)this.cmbHaarcascade.SelectedItem; foreach (var face in imgGray.DetectHaarCascade(hc.HaarCascade).First()) { var rect = face.rect; using (var g = Graphics.FromImage(img1)) { var w = Convert.ToSingle(rect.Width * 1.8); var h = Convert.ToSingle(rect.Height * 1.5); float x = rect.X - ((w - rect.Width) / 3); float y = rect.Y - (h - rect.Height) / 2 - rect.Height / 6; g.DrawImage(_yukkuri, x, y, w, h); } if (chkFaceDetect.Checked) { _img.Draw(rect, new Bgr(Color.Yellow), 1); img1 = _img.Bitmap; } } this.pictureBox1.Image = img1; } private void btnImage_Click(object sender, EventArgs e) { using (var ofd = new OpenFileDialog()) { ofd.FileName = "default.jpg"; ofd.InitialDirectory = @"D:\test\img\"; ofd.Filter = "画像ファイル(*.bmp;*.jpg;*.png)|*.bmp;*.jpg;*.png|すべてのファイル(*.*)|*.*"; ofd.FilterIndex = 1; ofd.Title = "対象の画像ファイルを選択してください"; ofd.RestoreDirectory = true; if (ofd.ShowDialog(this) == DialogResult.OK) { _img = new Image<Bgr, Byte>(ofd.FileName); ; _oldimg = _img.Clone(); this.pictureBox1.Image = _img.ToBitmap(); } } } private void btnSave_Click(object sender, EventArgs e) { if (this.pictureBox1.Image == null) return; using (var sfd = new SaveFileDialog()) { sfd.FileName = "新しいファイル.png"; sfd.InitialDirectory = @"C:\"; sfd.Filter = "PNGファイル(*.png)|*.png|すべてのファイル(*.*)|*.*"; sfd.FilterIndex = 1; sfd.Title = "保存先のファイルを選択してください"; sfd.RestoreDirectory = true; if (sfd.ShowDialog(this) == DialogResult.OK) this.pictureBox1.Image.Save(sfd.FileName, System.Drawing.Imaging.ImageFormat.Png); } } private void btnUndo_Click(object sender, EventArgs e) { if (_img == null) return; _img = _oldimg.Clone(); this.pictureBox1.Image = _oldimg.ToBitmap(); } } }
using Emgu.CV; namespace WindowsFormsApplication1 { public class ComboItem { private HaarCascade _haarCascade = null; private string _name = null; public ComboItem(HaarCascade haarCascade, string name) { _haarCascade = haarCascade; _name = name; } public HaarCascade HaarCascade { get { return _haarCascade;} } public string Name { get { return _name; } } public override string ToString() { return _name; } } }
今年のGWはゆっくりできそうです(´ー`)