一:前言废话
前两天在CSDN刷到个上首页推荐的利用python进行二寸照片换底色的博文,然后看着我丑不拉几(帅出天际)的二寸平头小照片,我也忍不住了。
想想从小学开始到上大学,拍了无数次一寸二寸的各种底色的证件照,每一个我都喜欢的不得了,但竟然没一份是留了电子版的,这可真是脑子进水了。
所以我只能每样留一张自己看了,但没电子版就意味着没法再印一样的证件照了。再看看我这越长越歪的颜值,再也拍不出以前的巅峰了。
所以我就手机拍一下然后各种PS,大概流程如下:
1:首先随便拍一张,保证清晰
2:然后剪切一下进行放大
3:然后进行图像透视矫正得到常规证件照
最后换底色就简简单单轻轻松松啦。
他们都说我拍的像刚从监狱出来一样,所以坚-决-不-露-脸-
今天整个流程的效果代码还没做完,但为了达到我暑期日更的小flag,今天就分享下如何给照片人脸打码。
这个功能用程序实现真的鸡肋的很呀,打码随便用手机点两下不就OK了,所以大家不必较真实用性,了解程序本身所涉及的知识点才是重点。
二:程序如何检测到人脸
人脸的识别当然方法很多啦,尤其是二寸照片这么简单直接的类型。不过实在没必要人脸检测,可以再观察下照片:
除了背景的蓝色就是衣服的白色后者头发的黑色还有皮肤的黄色,所以很好区分的嘛,可以利用直方图反投影,关于直方图反投影不清楚的可以点这篇:唉,再再再学一下直方图:直方图反投影
int main()
{
//【1】读取原图片以及投影模板
Mat srcImage, dstImage;
srcImage = imread("肖像.jpg",33);
imshow("原图", srcImage);
Mat RoiImage = imread("投影模板.png");
//【2】转HSV模型
Mat HsvImage, RoiImage_HSV;
cvtColor(srcImage, HsvImage, COLOR_BGR2HSV);
cvtColor(RoiImage, RoiImage_HSV, COLOR_BGR2HSV);
//【3】计算公路的直方图
MatND roiHist; //直方图对象
int dims = 2; //特征数目(直方图维度)
float hranges[] = { 0,180 }; //特征空间的取值范围
float Sranges[] = { 0,256 };
const float *ranges[] = { hranges,Sranges };
int size[] = { 20,32 }; //存放每个维度的直方图的尺寸的数组
int channels[] = { 0,1 }; //通道数
calcHist(&RoiImage_HSV, 1, channels, Mat(), roiHist, dims, size, ranges);
//【4】直方图归一化
normalize(roiHist, roiHist, 0, 255, NORM_MINMAX);
//【5】反向投影
Mat proImage; //投影输出图像
calcBackProject(&HsvImage, 1, channels, roiHist, proImage, ranges);
imshow("投影", proImage);
//图像掩码Mask操作
Mat maskImage;
threshold(proImage, maskImage, 1, 255, THRESH_BINARY);//对mask进行二值化,将mask进一步处理
imshow("二值腌膜", maskImage);
dstImage = Mat::zeros(srcImage.size(), CV_8UC3);
srcImage.copyTo(dstImage, maskImage);
imshow("掩码操作", dstImage);
waitKey();
return 0;
}
这段代码就是利用直方图得到了一幅mask图像,其中人脸部分为255,其余部分为0:
左图为直方图反投影之后得到的投影(灰度图),右图为对投影图进行二值化后得到的二值mask。
之后调用自定义打码函数对标记mask区域进行打码:
//自定义打码函数
Mat MosaicImage = trans2Mosaic(srcImage, maskImage, 10);
imshow("马赛克效果图", MosaicImage);
是不是很好看!感觉我就像变成了X战警钻石女王那样似的~
自定义函数代码,函数输入顺次为原图,图像腌膜,马赛克大小:
//原图,腌膜,马赛克大小
Mat trans2Mosaic(Mat srcImage, Mat maskImage, int half_patch) {
Point2d point;//
Mat frame, mask;
srcImage.copyTo(frame);
maskImage.copyTo(mask);
int height = frame.rows;//获取图像的长宽
int width = frame.cols;
//遍历图像,步长为马赛克大小
for (int x = 0; x < height; x = x + half_patch) {
for (int y = 0; y < width; y = y + half_patch) {
//如果不是标记区域,则跳过
if (mask.at<uchar>(x, y) ==0 ) {
continue;
}
else { //如果是标记区域,对像素进行重赋值
int b = theRNG().uniform(0, 255);
int g = theRNG().uniform(0, 255);
int r = theRNG().uniform(0, 255);
for (int dx = 0; dx < half_patch; dx++) {
for (int dy = 0; dy < half_patch; dy++) {
frame.at<Vec3b>(x + dx, y + dy)[0] = (uchar)b;
frame.at<Vec3b>(x + dx, y + dy)[1] = (uchar)g;
frame.at<Vec3b>(x + dx, y + dy)[2] = (uchar)r;
}
}
}
}
}
return frame;
}
该函数思路很简单,就是遍历图像像素点,遍历的步长为自定义的马赛克大小,然后判断该像素点是否属于标记(人脸)区域,否则跳过,是则以改点为起点遍历改点左侧以及下侧一个马赛克大小的区域,并将该区域像素随机赋值。
THE END