最近有个需求比如手机拍照制作证件照
如果不用第三方提供的收费服务比如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