意外に簡単?swiftで動的にカメラ内の顔を検出して処理する(CIDetector)
最近流行りのVRもいいけど、現実が全部嫁になればいいのにと日々思っているこの頃
そんな幻想を抱きながら顔を置き換える処理を、Swiftで簡単に顔検出が出来るみたいなのでやってみました
class TestViewController: UIViewController,UIGestureRecognizerDelegate,AVCaptureVideoDataOutputSampleBufferDelegate { let captureSession = AVCaptureSession() let videoDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo) let audioDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeAudio) //let fileOutput = AVCaptureMovieFileOutput() var videoOutput = AVCaptureVideoDataOutput() var hideView = UIView() override func viewDidLoad() { super.viewDidLoad() //各デバイスの登録(audioは実際いらない) do { let videoInput = try AVCaptureDeviceInput(device: self.videoDevice) as AVCaptureDeviceInput self.captureSession.addInput(videoInput) } catch let error as NSError { print(error) } do { let audioInput = try AVCaptureDeviceInput(device: self.audioDevice) as AVCaptureInput self.captureSession.addInput(audioInput) } catch let error as NSError { print(error) } self.videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey : Int(kCVPixelFormatType_32BGRA)] //フレーム毎に呼び出すデリゲート登録 let queue:dispatch_queue_t = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL); self.videoOutput.setSampleBufferDelegate(self, queue: queue) self.videoOutput.alwaysDiscardsLateVideoFrames = true self.captureSession.addOutput(self.videoOutput) let videoLayer : AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession) videoLayer.frame = self.view.bounds videoLayer.videoGravity = AVLayerVideoGravityResizeAspectFill self.view.layer.addSublayer(videoLayer) //カメラ向き for connection in self.videoOutput.connections { if let conn = connection as? AVCaptureConnection { if conn.supportsVideoOrientation { conn.videoOrientation = AVCaptureVideoOrientation.Portrait } } } hideView = UIView(frame: self.view.bounds) self.view.addSubview(hideView) self.captureSession.startRunning() } func imageFromSampleBuffer(sampleBuffer: CMSampleBufferRef) -> UIImage { //バッファーをUIImageに変換 let imageBuffer: CVImageBufferRef = CMSampleBufferGetImageBuffer(sampleBuffer)! CVPixelBufferLockBaseAddress(imageBuffer, 0) let baseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0) let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer) let width = CVPixelBufferGetWidth(imageBuffer) let height = CVPixelBufferGetHeight(imageBuffer) let colorSpace = CGColorSpaceCreateDeviceRGB() let bitmapInfo = (CGBitmapInfo.ByteOrder32Little.rawValue | CGImageAlphaInfo.PremultipliedFirst.rawValue) let context = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, bitmapInfo) let imageRef = CGBitmapContextCreateImage(context) CVPixelBufferUnlockBaseAddress(imageBuffer, 0) let resultImage: UIImage = UIImage(CGImage: imageRef!) return resultImage } func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, fromConnection connection: AVCaptureConnection!) { //同期処理(非同期処理ではキューが溜まりすぎて画像がついていかない) dispatch_sync(dispatch_get_main_queue(), { //バッファーをUIImageに変換 var image = self.imageFromSampleBuffer(sampleBuffer) let ciimage:CIImage! = CIImage(image: image) //CIDetectorAccuracyHighだと高精度(使った感じは遠距離による判定の精度)だが処理が遅くなる var detector : CIDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options:[CIDetectorAccuracy: CIDetectorAccuracyLow] ) var faces : NSArray = detector.featuresInImage(ciimage) // 検出された顔データを処理 for subview:UIView in self.hideView.subviews { subview.removeFromSuperview() } var feature : CIFaceFeature = CIFaceFeature() for feature in faces { // 座標変換 var faceRect : CGRect = feature.bounds var widthPer = (self.view.bounds.width/image.size.width) var heightPer = (self.view.bounds.height/image.size.height) // UIKitは左上に原点があるが、CoreImageは左下に原点があるので揃える faceRect.origin.y = image.size.height - faceRect.origin.y - faceRect.size.height //倍率変換 faceRect.origin.x = faceRect.origin.x * widthPer faceRect.origin.y = faceRect.origin.y * heightPer faceRect.size.width = faceRect.size.width * widthPer faceRect.size.height = faceRect.size.height * heightPer // 顔を隠す画像を表示 let hideImage = UIImageView(image:UIImage(named:"trump.jpg")) hideImage.frame = faceRect self.hideView.addSubview(hideImage) } }) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
左が参考オバマ、右が処理後
感想
・今の状態だとiphone越しなのでVRみたいにステレオマッチングしたら面白そう、ゴーグルかけて隠す画像も3Dモデルにして、顔の向きに応じて3Dモデルも回転したら・・・
・実際上書きしたところを想像すると、もしストローとかポッキーを相手が咥えていたら刺さりそう
・正面画像の検出精度はLowでもなかなかだが、横顔はかなり厳しい印象
・細かい精度を求めないのであれば、かなり簡単なので結構応用出来そう
・CIDetectorではなくOpenCVだと顔以外も検出できるらしい
参考
swiftでAVCaptureVideoDataOutputを使ったカメラのテンプレート - Qiita
twilioを使って友達がいるアピールをしよう!
twilioについては↓
Twilio for KDDI Web Communications | クラウド電話API
これを使い自分に電話を掛け、あたかも友達から電話が掛ってきたように見せかける。
まず契約だが、twilioには1番号につき月108円(友達料)が必要だ。
缶ジュース1本で友達が出来るなら安いだろう。
取得した番号を携帯の連絡先に登録することも忘れるなよ!
番号を取得した後 AccountSid と AuthToken が貰えるので控えておこう。
今回はPHPを利用する。
1.自分のサーバー上に↓のライブラリを入れる。(サーバーない人はAWSやらHerokuやらgoogle cloud platformやらで作っておこう)
2.ライブラリを呼び出し電話をかけるphpを書く
<?
require('/path/to/twilio-php/Services/Twilio.php');
$account_sid = '[取得したAccountSid]';
$auth_token = '[取得したAuthToken]';
$client = new Services_Twilio($account_sid, $auth_token);
'FallbackMethod' => 'GET',
'StatusCallbackMethod' => 'GET',
'Record' => 'false'
?>
3.xmlファイルを作りに電話でなにをするか書く。
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say language="ja-jp">はじめまして</Say>
</Response>
↑の例では機械音声を流しているだけだが、女の子が入っている音声ファイルを流せば彼女と会話しているようにも出来るぞ!
4.自分で2のphpにアクセスする、またはクーロンとかで自動で指定時間にphpを実行するようにする。
※自分でアクセスする場合はセキュリティに気をつけろ!不正利用されて高額請求が来ても知りません。
5.自分の携帯に電話がかかってくるので、友達(録音)と話すだけ!
これで友達がいるアピールがいつでもできる!やったね!
・・・。
カードヒーローという最高のゲーム
記事の目的
古いゲームですが面白いので、もっといろんな人にプレイしてほしい
※カードゲームのプレイ歴はポケモンカード、遊戯王、大貝獣物語 THE MIRACLE OF THE ZONE、hearthstoneぐらい。ギャザとかカルドセプトとはやったことないです。
カードヒーローとは?
『トレード&バトル カードヒーロー』は、任天堂が2000年2月21日に発売したゲームボーイ用のコンピュータゲームソフト。
トレーディングカードゲームを題材としたオリジナルゲームである。ゲームボーイカラー対応。
現在バーチャルコンソールで販売中。
2007年12月20日に続編が発売。(wiki参照)
バーチャルコンソール トレード&バトル カードヒーロー|ニンテンドー3DS|Nintendo
何が面白いの?
カードゲームの内容だけでなく、コンピュータゲーム全体として良かった点をあげてみます。
1.オートセーブ機能
データが一つしかなく常にオートセーブされています。
対戦中に電源を切っても、切ったターンから始まります。
逆に言うと、どんなに負けそうでも対戦をなかったことにはできません。
64のシレン2のオートセーブと同じようなものです。(こっちはコントローラーパックという手もありますが・・・)
しかも勝率が付きます。
それまでのコンピュータゲームでは、負けそうになってもセーブデータを読み込みなおせば対戦をやり直すことが出来ました。
しかし、オートセーブのおかげでより対戦に緊張感が生まれます。
人生みたいなものですね。何回もやり直しできる人生もつまらなそうです。
人によってこの機能は賛否両論だとは思いますが、個人的にはこの機能のおかげでかなり面白さが増していると思います。
2.ストーリー
当時においては、他のコンピュータカードゲームに比べるとカードゲームをして楽しむ、というだけでなく物語としても続きが気になり楽しめる内容になっていました。(ネタばれにつき省略)
現在においては、古いゲーム+オリジナルということで、登場人物やストーリー等何の情報も得ないままプレイすることができます。
最近の有名なゲームであれば事前情報がある程度出された状態でゲームすることが多く、ネット上には広告が出されまったく知らないままプレイすることが難しい時代になってきています。個人的にはすごく大事。
(シュタゲネタばれしたやつ一生許さんからな!!)
3.覚えやすさ(ネタばれ含む)
新しくカードゲームを覚えるというのはとてもコストがかかります。
カードの効果一つにしても遊戯王やギャザだと何百、何千とあります。
カードヒーローの場合110枚しかありません。
その分戦略の幅が少ないと言われるかもしれませんが、カードヒ―ローの最大の面白さは駆け引き(後述)にあると思ってます。
ルールにおいても、チュートリアルがとてもしっかりしていて、「このカードどう使うの?」といったことにはなりません。
4.配置型カードゲーム(ネタばれ含む)
初期の手札は5枚
カードの種類は
- マスターカード(プレーヤーの分身)
- モンスターカード
- マジックカード(手札から直接使うカード)
があります。
そして下記のような形のゲーム版で行われます
後 後
前 相 前
-------------
前 自 前
後 後
自/相は各プレーヤーの分身のマスターカードが置かれ、
前/後にモンスターカードを置いて戦います。
モンスターカードには攻撃できる距離が設定されており、
どこに配置するかを後々の行動も含め考えるて戦う必要があります。
マスターカードにはHPがあり(3~10)、先に0にした方が勝利です。
そして一番大事なストーンという概念がこのゲームがあります。
各ターンに3個ずつ配られ、そのストーンを使いつつ対戦します。
例
・消費
モンスターを場に出すのにストーンを消費
モンスターのレベルアップでストーンを消費
マスターカードの特殊能力でストーンを消費
マジックカード使用時にストーンを消費
・増える
各ターンに3個ずつ増える
マスターカードがダメージを受けるとストーンが増える
自分のモンスターがやられるとストーンが増える
マスターカードの特殊能力でストーンが増える
このように、ストーンの増えたり減ったりを計算して対戦していくのがとても楽しい。
よくあるのカードゲームにある、カードを出して強さを競うだけというのとは違いここに駆け引きが生まれます。
ここで相手を攻撃するとストーンが増えるので攻撃しないでおこうとか。
逆に、相手に攻撃をさせるよう仕向けたり等さまざまあります。
5.細かい要素(ネタばれ含む)
ゲーム内で細かい要素が多々あります。
- 現実時間で1日が経過するとお母さんから1日50円もらえる
- カード同士を合成して別のカードを生み出せたり出来る
- ゲーム内にカードショップがあり実際にパックを買ったり売ったりもできる
- ゲーム内のプレイヤーとのランキング機能
などなど
いいことばかり言ってるけど悪い点も言えよ!
もちろん、今から始めるにあたり思うところはあるので上げていきます。
・やっぱりカードの種類が少ない
昔のゲームということもありしょうがないとも言えますが、遊戯王やギャザのように長く遊ぶという点においてはいささか物足りなさがあります。
・ユーザーとの対戦がまずできない
カードゲーム自体の面白さはここにあると思います。
ですが人対人というのがもうできません。
昔は続編でWIFIバトルなどができましたが今では出来ないです。(リアルのカードも昔はありました)
なので、デッキを作ったので誰かと勝負しようというわけにはいきません。
・グラフィックの問題
GBなので現在においてはグラフィック面はかなりさびしいです。
そこがいいという場合もありますが。
・CPUの考える時間
今の時代どんなことも早く早くという時代になってきました、
そういう人にとって当時のCPUの考える時間はとても長く感じるかもしれません。
まとめ
悪いことを後に書くと悪い方に目が行きがちですが、それを差し引いてもとても面白いです。
「思いで補正じゃない?」と言われればそれまでですが、すこしでも多くの人に楽しんでもらえれば嬉しいです。
また、昔やったことある人も思い出してもらえたら嬉しいです。