LoginSignup
34
36

More than 5 years have passed since last update.

SwiftでCoreGraphicsを使った自作クロマキー合成フィルタの作成

Last updated at Posted at 2015-01-28

はじめに

人物の背景を切り出して合成したい場合など、
SF映画ではクロマキー合成を使って表現されています。

iOSのCIFilterにも似たようなものがないか探したのですが、
見当たらなかったので、簡単に作れる範囲で作成してみました。
クロマキー合成のhowtoには諸説あるようなので、あまり難しい事は抜きで
画像データをピクセル走査するサンプルとして考えて頂ければと思います。

方法

chromakey_howto.png

タッチした場所の<色A>を取得

<色A> = 白 / それ以外 = 黒 の<二値化画像B>を生成

元画像と<二値化画像B>を合成して、差分の黒色のみ切り抜きを行う

背景画像と合成

1:まずタッチした場所の色(UIColor)を取得するコードを描きます。

UIImageのextensionとして作成します。

<ポイント>
RGBA各1byteなので1pixelは4byte
(var pixelInfo: Int = ((Int(self.size.width) * Int(pos.y)) + Int(pos.x)) * 4)

extension UIImage {
    func getPixelColor(pos: CGPoint) -> UIColor {        
        var pixelData = CGDataProviderCopyData(CGImageGetDataProvider(self.CGImage))
        var data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
        var pixelInfo: Int = ((Int(self.size.width) * Int(pos.y)) + Int(pos.x)) * 4
        var r = CGFloat(data[pixelInfo]) / CGFloat(255.0)
        var g = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
        var b = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
        var a = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)
        return UIColor(red: r, green: g, blue: b, alpha: a)
    }
}

2.次に、UIImageの縦横を1pixずつ全走査して、対象の色(1で取得したColor)であれば白、違えば黒の

UIImageを返すようなextensionを書きます。

<ポイント>
画像の幅、高さの分だけforでまわして、point = (x, y)の色を取得する
RGBA各1byteなので1pixelは4byte(let newByteLength = _width * _height * 4)
CGImageCreate(RGBバイト配列から、UIImageを作る)を使って、UIImageを返す

pixcel_image.png

//入力した色と同じ色を白に塗りつぶす
func getMaskImageFromTappedColor(_tColor:UIColor) -> UIImage? {
    var _image = self
    var _width = Int(_image.size.width)
    var _height = Int(_image.size.height)
    var _imageData = _image.imageData()
    var imageBytes : UnsafeMutablePointer<Byte>;
    let newByteLength = _width * _height * 4
    imageBytes = UnsafeMutablePointer<Byte>.alloc(newByteLength)
    var _cnt = 0;
    for x in 0..<_width {
        for y in 0..<_height {
            var point = (x, y)
            var color = UIImage.colorAtPoint(
                point,
                imageWidth: _width,
                withData: _imageData
            )
            var i: Int = ((Int(_width) * Int(y)) + Int(x)) * 4
            if(color == _tColor){
                imageBytes[i] = Byte(255) // red
                imageBytes[i+1] = Byte(255); // green
                imageBytes[i+2] = Byte(255); // blue
                imageBytes[i+3] = Byte(255); // alpha
            }else{
                imageBytes[i] = Byte(0) // red
                imageBytes[i+1] = Byte(0); // green
                imageBytes[i+2] = Byte(0); // blue
                imageBytes[i+3] = Byte(255); // alpha
            }
        }
    } 
    var provider = CGDataProviderCreateWithData(nil,imageBytes, UInt(newByteLength), nil)
    var bitsPerComponent:UInt = 8
    var bitsPerPixel:UInt = bitsPerComponent * 4
    var bytesPerRow:UInt = UInt(4) * UInt(_width)
    var colorSpaceRef = CGColorSpaceCreateDeviceRGB()
    var bitmapInfo = CGBitmapInfo.ByteOrderDefault
    var renderingIntent = kCGRenderingIntentDefault
    var cgImage = CGImageCreate(UInt(_width), UInt(_height), bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, nil, false, renderingIntent)
    return UIImage(CGImage: cgImage)
}

3.二値化された画像から、元画像を透過する関数をextensionに作る

func getMaskedImage(maskImage:UIImage!) -> UIImage {        
    let maskImageReference:CoreImage.CGImage? = maskImage?.CGImage
    let mask = CGImageMaskCreate(CGImageGetWidth(maskImageReference),
        CGImageGetHeight(maskImageReference),
        CGImageGetBitsPerComponent(maskImageReference),
        CGImageGetBitsPerPixel(maskImageReference),
        CGImageGetBytesPerRow(maskImageReference),
        CGImageGetDataProvider(maskImageReference),nil,false)
    let maskedImageReference = CGImageCreateWithMask(self.CGImage, mask)
    let maskedImage = UIImage(CGImage: maskedImageReference)
    return maskedImage!
}

4.最後にUIViewにtoucheBeganメソッドを加えて、タッチした座標をextensionに渡す処理を書く

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {        
    let t = touches.anyObject() as UITouch
    //指定したのImageViewの中での座標を取得する
    let point = t.locationInView(self.shipImageView)
    //get color you touched
    var color : UIColor! = self.shipImageView?.image!.getPixelColor(point)
    var filteredImage = self.shipImageView?.image!.getFilteredImage(color)
    self.shipImageView?.image = filteredImage
}

完成

タッチした場所の色を背景として透過するサンプルが作成されました。
chromakey_sample.png

おしまい

今回はタッチした場所の色を抜く事で背景を切り取りましたが、
ニ値化画像の作り方次第で、色々な切り抜き方が可能だと思います。
例えば画像の明るさという観点で、しきい値を作成し、二値化画像を作る事や、
iOSの顔認識で顔の部分だけ丸く切り抜くような二値化画像を作るなどなど
色々と楽しめそうです!

(github)
https://github.com/oggata/PhotoChromakeyDemo

34
36
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
36