ようこそ。睡眠不足なプログラマのチラ裏です。

C#からOpenCVで顔を検出して"ゆっくり"してみよう

なにやら↓こういうのが流行っていたようですね。

元ネタ
Perlでアニメ顔を検出&解析するImager::AnimeFace
Yukkurizer - アニメキャラゆっくり化CGI

 
何かを受信(^o^)ノ
 

 
*1


ゆっくりしてみよう

というわけで、やつけで適当に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はゆっくりできそうです(´ー`)


ちなみに

顔の大きさに合わせた感じでゆっくりします。
 

 

 

*1:この画像では1人のおっさんが認識されなかった模様

*2:試してはいませんがMonoでも利用できるみたいです