图像处理方面的一些探索

最近有个需求比如手机拍照制作证件照

如果不用第三方提供的收费服务比如face++的抠图,想到的大致步骤应该是

1:找一个白色的背景墙,衣服最好和背景墙区分开来
2:然后扣出来
3:最后和其他底色或者图片融合到一起

一下就想到了OpenCV 这个处理图片的框架,因为里面内置了很多成熟的算法

iOS方面安装方法现在可以用Pods 进来,值得注意的是由于需要C++ 代码和iOS混编,所以当前文件改为.mm后缀,并引入相应的头文件例如:

#import <opencv2/opencv.hpp>
#import <opencv2/imgcodecs/ios.h>

抠图OpenCV有相应的算法用到的是grabCut方法大致步骤是:

1:基于交互式界面由用户选择前景区域;
2:定义一个单通道的输出掩码,0为背景,1为前景,2为可能的背景,3为可能的前景;
3:grabCut抠图;将输出结果与可能的前景作比较得到可能的前景;
4:定义三通道的结果图像;
5:从原图中拷贝可能的前景到结果图像;

API函数

grabCut( InputArray img, InputOutputArray mask, Rect rect,
                           InputOutputArray bgdModel, InputOutputArray fgdModel,
                           int iterCount, int mode = GC_EVAL );

解释:

img:输入原图像;

mask:输出掩码;

rect:用户选择的前景矩形区域;

bgModel:输出背景图像;

fgModel:输出前景图像;

iterCount:迭代次数;

知道大致步骤开始干,由于C++ image 和 iOS image表示方法不一样,所以大致有下面两个转化方法

-(UIImage *)UIImageFromCVMat:(cv::Mat)cvMat
{
    NSData *data = [NSData dataWithBytes:cvMat.data length:cvMat.elemSize()*cvMat.total()];
    CGColorSpaceRef colorSpace;
    
    if (cvMat.elemSize() == 1) {
        colorSpace = CGColorSpaceCreateDeviceGray();
    } else {
        colorSpace = CGColorSpaceCreateDeviceRGB();
    }
    
    CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
    
    // Creating CGImage from cv::Mat
    CGImageRef imageRef = CGImageCreate(cvMat.cols,                                 //width
                                        cvMat.rows,                                 //height
                                        8,                                          //bits per component
                                        8 * cvMat.elemSize(),                       //bits per pixel
                                        cvMat.step[0],                            //bytesPerRow
                                        colorSpace,                                 //colorspace
                                        kCGImageAlphaNone|kCGBitmapByteOrderDefault,// bitmap info
                                        provider,                                   //CGDataProviderRef
                                        NULL,                                       //decode
                                        false,                                      //should interpolate
                                        kCGRenderingIntentDefault                   //intent
                                        );
    
    
    // Getting UIImage from CGImage
    UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);
    CGDataProviderRelease(provider);
    CGColorSpaceRelease(colorSpace);
    
    return finalImage;
}
- (cv::Mat)cvMatFromUIImage:(UIImage *)image
{
    CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
    CGFloat cols = image.size.width;
    CGFloat rows = image.size.height;
    
    cv::Mat cvMat(rows, cols, CV_8UC4); // 8 bits per component, 4 channels (color channels + alpha)
    
    CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,                 // Pointer to  data
                                                    cols,                       // Width of bitmap
                                                    rows,                       // Height of bitmap
                                                    8,                          // Bits per component
                                                    cvMat.step[0],              // Bytes per row
                                                    colorSpace,                 // Colorspace
                                                    kCGImageAlphaNoneSkipLast |
                                                    kCGBitmapByteOrderDefault); // Bitmap info flags
    
    CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
    CGContextRelease(contextRef);
    
    return cvMat;
}

注意:网上找的一些其他平台的写法在iOS 中要加上cv:: 前缀,比如变量的声明或者方法的调用。

主要的方法:

-(UIImage*) doGrabCutWithMask:(UIImage*)sourceImage maskImage:(UIImage*)maskImage iterationCount:(int) iterCount{

    cv::Mat img=[self cvMatFromUIImage:sourceImage];
    cv::cvtColor(img , img , CV_RGBA2RGB);
    
    cv::Mat1b markers=[self cvMatMaskerFromUIImage:maskImage];
    cv::Rect rectangle(0,0,0,0);
    // GrabCut segmentation
    cv::grabCut(img, markers, rectangle, bgModel, fgModel, iterCount, cv::GC_INIT_WITH_MASK);
    
    cv::Mat tempMask;
    cv::compare(mask,cv::GC_PR_FGD,tempMask,cv::CMP_EQ);
    // Generate output image
    cv::Mat foreground(img.size(),CV_8UC3,
                       cv::Scalar(255,255,255));
    
    tempMask=tempMask&1;
    img.copyTo(foreground, tempMask);
    
    
    UIImage* resultImage=[self UIImageFromCVMat:foreground];
    
    
    return resultImage;
}

解释:

1:从iOS平台导入image转化为C++ 的结构
2:调用grabCut抠图
3:比较mask的值为可能的前景像素才输出到mask中
4:产生输出图像
5:将原图区域copy到foreground中
6:转化为iOS平台的image结构

看效果

处理前

处理后

总结

由于背景色和前景色有明显对比所以看着有锯齿的边缘,解决思路是把当前图像和背景图融合的时候边缘做腐蚀加高斯模糊处理。
总结下上述做法适合比较规范的拍照姿势,如果不规范就会很难看,所以打算放弃这个思路。

后来突然发现一个提供制作证件的API,但是返回的图像是有水印的,这是我想直接把水印去掉不就OK了吗?

还是用OpenCV这个框架用了里面的inpaint修复算法。

大致步骤:

1:标定噪声的特征,使用cv2.inRange二值化标识噪声对图片进行二值化处理,具体代码:cv2.inRange(img, np.array([240, 240, 240]), np.array([255, 255, 255])),把[240, 240, 240]~[255, 255, 255]以外的颜色处理为0;
2:使用inpaint方法,把噪声的mask作为参数,推理并修复图片;

代码如下:

    cv::Mat img=[self cvMatFromUIImage:[UIImage imageNamed:@"test.jpg"]];
    cv::cvtColor(img , img , CV_RGBA2RGB);
//    UIImage *testImage = [UIImage imageNamed:@"test.jpg"];
//    UIImage *reslutsImage = [testImage WaterMarkDelete:CGRectMake(0, 0, 320, 320)];

//
//
//     //获取mask
//
//    cv::Mat mask;
//
//    cv::inRange(img, cv::Scalar(0, 0, 250), cv::Scalar(0, 0, 255), mask);
//
//
//
//    // 修复
//
//    cv::Mat dst;
//
//    cv::inpaint(img, mask, dst, 3, CV_INPAINT_TELEA);
//
//   UIImage *resultImage =[self UIImageFromCVMat:dst];
//   UIImageWriteToSavedPhotosAlbum(resultImage, self, @selector(image:didFinishSavingWithError:contextInfo:), NULL);
//
    
    cv::Mat wm; // 水印文字


    cv::inRange(img, cv::Scalar(204, 115, 122), cv::Scalar(246, 103, 115), wm);
    
    UIImage *wmImage =[self UIImageFromCVMat:wm];
    UIImageWriteToSavedPhotosAlbum(wmImage, self, @selector(image:didFinishSavingWithError:contextInfo:), NULL);
//
    // 形态学操作

    cv::Mat kernel = getStructuringElement(MORPH_RECT, cv::Size(3, 3), cv::Point(-1, -1));

    morphologyEx(wm, wm, MORPH_DILATE, kernel, cv::Point(-1, -1), 2);



    // 去水印结果

    cv::Mat tywwm;

    inpaint(img, wm, tywwm, 3, CV_INPAINT_NS);
       UIImage *resultImage =[self UIImageFromCVMat:tywwm];
       UIImageWriteToSavedPhotosAlbum(resultImage, self, @selector(image:didFinishSavingWithError:contextInfo:), NULL);

看效果:

处理的过程图

注意的是

图像形态学操作膨胀得到的是第二张的图片,如果水印的颜色是其他颜色则需要调整cv2.inRange 颜色范围。

总结

由于时间有限我感觉没个一个月时间是不能达到产品的效果,所以这个方式我也暂时放弃,以后可以作为自己的一个App来做。

再探索的过程中发现了一些不错的框架,或者思路处理图片。

1:比如iOS平台的coreimage,可以做直播主播的绿幕替换,其实主播背景都是后期处理上去的,这样的情况比较适合比较单纯的纯色,如果有其他亮度的效果,可能效果不佳给个链接https://juejin.im/post/5a3a10baf265da43252971b5

2:除去水印谷歌貌似研究出来了一个新的算法可以比PS还要高效的处理去掉水印,https://www.jiqizhixin.com/articles/2017-08-19-5

3:还有一个不错的框架CPUImage对图片和视频的处理也是一个很强大的框架

4:推荐一个不错的图片处理系列教程:https://www.cnblogs.com/Imageshop/

参考链接:

https://blog.csdn.net/huanghuangjin/article/details/81459077

https://blog.csdn.net/qq_24946843/article/details/82827168

https://blog.csdn.net/ahgdwang/article/details/79952235

https://blog.csdn.net/qq_26907755/article/details/81780519

https://cloud.tencent.com/developer/article/1156628

https://www.jiqizhixin.com/articles/2017-08-19-5

https://www.icefox.org/2017/02/10/opencv%E4%B8%AD%E5%9B%BE%E5%83%8F%E4%BF%AE%E5%A4%8D%E6%8A%80%E6%9C%AF%E4%BB%8B%E7%BB%8D%E4%B8%8E%E6%BC%94%E7%A4%BA/

https://github.com/naver/grabcutios

https://github.com/ahgdwang/WaterMarkDelete

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容