当前位置: > > > Swift - 抠图,及图片合成功能的实现(适用于纯色背景)

Swift - 抠图,及图片合成功能的实现(适用于纯色背景)

(本文代码已升级至Swfit3)

大家肯定都用过PS进行抠图(扣图),而在Swift中,使用代码也可以实现抠图功能。

1,要把一个人物或物体从背景中抠出来,通常有两种办法:
(1)使用CoreImage色域:适合纯色背景(或者背景色相对单一,色差不会太大),抠图精准
(2)使用openCv边缘检测:复杂背景情况也适用,默认抠图不够精确

2,下面使用第一种方案把下面的小猫抠出来,放置到雪地背景上。
 

3,如何使用Core Image抠图
对于纯色背景,可以直接把背景色给消除,这样剩下的便是主体了。要消除背景色,可以使用CIColorCube滤镜。
而CIColorCube滤镜需要一张cube映射表,这张表其实就是张颜色表(3D颜色查找表),把你想消除的颜色的alpha值设置为0,其他的颜色不变,Core Image将会把图像数据上的颜色映射为表中的颜色,以此来达到消除某种颜色的目的。

4,代表颜色值区域的HSV(Hue值)图

通过这个可以很方便的查看RGB颜色对应的HSV值。比如小猫背景都是蓝色的(只不过深浅不一),我们只需要把HSV在210到240这段颜色去处即可。

5,下面是样例效果图
为便于比较,我这边分别做了“只抠图”,以及“抠图并更换背景”两个功能。(真是毫无PS痕迹)


6,代码如下:
(1)首先创建Cube Map表
新建一个“C File”文件CubeMap.c,同时Xcode会自动生成这个C文件对应的头文件CubeMap.h,还有连接头文件(Bridging Header文件)。各文件里代码如下:
--- CubeMap.c ---
#include "CubeMap.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

void rgbToHSV(float *rgb, float *hsv) {
    float min, max, delta;
    float r = rgb[0], g = rgb[1], b = rgb[2];
    float *h = hsv, *s = hsv + 1, *v = hsv + 2;
    
    min = fmin(fmin(r, g), b );
    max = fmax(fmax(r, g), b );
    *v = max;
    delta = max - min;
    if( max != 0 )
        *s = delta / max;
    else {
        *s = 0;
        *h = -1;
        return;
    }
    if( r == max )
        *h = ( g - b ) / delta;
    else if( g == max )
        *h = 2 + ( b - r ) / delta;
    else
        *h = 4 + ( r - g ) / delta;
    *h *= 60;
    if( *h < 0 )
        *h += 360;
}

struct CubeMap createCubeMap(float minHueAngle, float maxHueAngle) {
    const unsigned int size = 64;
    struct CubeMap map;
    map.length = size * size * size * sizeof (float) * 4;
    map.dimension = size;
    float *cubeData = (float *)malloc (map.length);
    float rgb[3], hsv[3], *c = cubeData;
    
    for (int z = 0; z < size; z++){
        rgb[2] = ((double)z)/(size-1); // Blue value
        for (int y = 0; y < size; y++){
            rgb[1] = ((double)y)/(size-1); // Green value
            for (int x = 0; x < size; x ++){
                rgb[0] = ((double)x)/(size-1); // Red value
                rgbToHSV(rgb,hsv);
                // Use the hue value to determine which to make transparent
                // The minimum and maximum hue angle depends on
                // the color you want to remove
                float alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) ? 0.0f: 1.0f;
                // Calculate premultiplied alpha values for the cube
                c[0] = rgb[0] * alpha;
                c[1] = rgb[1] * alpha;
                c[2] = rgb[2] * alpha;
                c[3] = alpha;
                c += 4; // advance our pointer into memory for the next color value
            }
        }
    }
    map.data = cubeData;
    return map;
}

--- CubeMap.h ---
#ifndef CubeMap_h
#define CubeMap_h

#include <stdio.h>

typedef struct CubeMap CubeMap; 

struct CubeMap {
    int length;
    float dimension;
    float *data;
};

struct CubeMap createCubeMap(float minHueAngle, float maxHueAngle);

#endif /* CubeMap_h */

--- Bridging-Header.h ---
#import "CubeMap.h"

(2)主页代码
import UIKit

class ViewController: UIViewController{
    
    @IBOutlet weak var imageView: UIImageView!
    
    //图片原图
    lazy var originalImage: UIImage = {
        return UIImage(named: "cat.jpg")
        }()!
    
    lazy var context: CIContext = {
        return CIContext(options: nil)
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        imageView.image = originalImage
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    //抠图
    @IBAction func cutOut(_ sender: AnyObject) {
        let cubeMap = createCubeMap(210,240)
        let data = NSData(bytesNoCopy: cubeMap.data, length: Int(cubeMap.length),
                          freeWhenDone: true)
        
        //消除某种颜色
        let colorCubeFilter = CIFilter(name: "CIColorCube")!
        colorCubeFilter.setValue(cubeMap.dimension, forKey: "inputCubeDimension")
        colorCubeFilter.setValue(data, forKey: "inputCubeData")
        colorCubeFilter.setValue(CIImage(image: originalImage), forKey: kCIInputImageKey)
        let outputImage = colorCubeFilter.outputImage
        
        let cgImage = context.createCGImage(outputImage!, from: outputImage!.extent)
        imageView.image = UIImage(cgImage: cgImage!)
    }
    
    //抠图并合成
    @IBAction func cutOutAndCompose(_ sender: AnyObject) {
        let cubeMap = createCubeMap(210,240)
        let data = NSData(bytesNoCopy: cubeMap.data, length: Int(cubeMap.length),
                          freeWhenDone: true)
        
        //消除某种颜色
        let colorCubeFilter = CIFilter(name: "CIColorCube")!
        colorCubeFilter.setValue(cubeMap.dimension, forKey: "inputCubeDimension")
        colorCubeFilter.setValue(data, forKey: "inputCubeData")
        colorCubeFilter.setValue(CIImage(image: originalImage), forKey: kCIInputImageKey)
        var outputImage = colorCubeFilter.outputImage
        
        //与背景图合成
        let sourceOverCompositingFilter = CIFilter(name: "CISourceOverCompositing")!
        sourceOverCompositingFilter.setValue(outputImage, forKey: kCIInputImageKey)
        sourceOverCompositingFilter.setValue(CIImage(image: UIImage(named: "bg.jpg")!),
                                             forKey: kCIInputBackgroundImageKey)
        outputImage = sourceOverCompositingFilter.outputImage
        
        let cgImage = context.createCGImage(outputImage!, from: outputImage!.extent)
        imageView.image = UIImage(cgImage: cgImage!)
    }
    
    //还原图片
    @IBAction func resetImage(_ sender: AnyObject) {
        self.imageView.image = originalImage
    }
}

7,源码下载:hangge_904.zip
评论3
  • 3楼
    2016-12-12 21:21
    Nathan

    感谢站长这么用心回复,另外还有个问题想请教一下:
    这个方法怎么抠白色和黑色背景的图片?
    或者swift有其他单独针对黑白的方法吗?
    ps:因为白色是通过调整饱和度来的

    站长回复

    白色或黑色背景的话有另外的方法,后面我会写篇相关文章,你可以关注下。

  • 2楼
    2016-12-01 14:40
    Nathan

    ```
    duplicate symbol _createCubeMap in:
    /Users/nathan/Library/Developer/Xcode/DerivedData/Koutu2-eplaxzssfxopryhhxjukijapxifp/Build/Intermediates/Koutu2.build/Debug-iphonesimulator/Koutu2.build/Objects-normal/x86_64/CubeMap.o
    /Users/nathan/Library/Developer/Xcode/DerivedData/Koutu2-eplaxzssfxopryhhxjukijapxifp/Build/Intermediates/Koutu2.build/Debug-iphonesimulator/Koutu2.build/Objects-normal/x86_64/ViewController.o
    ld: 1 duplicate symbol for architecture x86_64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    ```
    你好,请问下在swift3中测试本案例的时候出现如上错误,google、百度、stackoverflow搜遍了也没找到解决办法,如果有时间的回复的话非常感谢。

    站长回复

    试了下使用XCode8确实会出现你说的问题。我现在把代码修改了下,你可以再试试看。

  • 1楼
    2016-04-07 17:40
    eva

    这个压缩文件解压不出来

    站长回复

    多谢提醒,上传的压缩包有问题,现已修复。