一、基于Visual Studio的图片卡通化
二、基于Visual Studio的肤色改变
最近由于工作比较忙,很抱歉没有抽出时间来更新这个系列的博客。在这里感谢一下关注该博客的同学。其实写这个系列的初衷,主要还是想记录读书的过程,同时给自己一些动力坚持下去,没有期望关注度。但是大家的关注将给我更大的动力坚持下去。以后也会尽量抽时间更新后面的章节。
书归正传,今天继续完成第一章的最后一部分的内容:Android代码的移植。
关于本书第一章所涉及到的知识点,其实在前两部分已经介绍的差不多了,本节的重点在于介绍在app集成opencv的方法和基本调用流程。没有什么理论知识,大部分是程序化的流程。另外,这部分要求对android开发有一些基础。如果你对android开发没有兴趣,或是只想了解opencv或本书涉及到的图像处理知识,这一节可以直接忽略。
老规矩,先上效果图。这张照片是对着显示器拍的,纹理比较明显,局部效果可能不是很好。有兴的同学可以将demo下载下来,自拍玩一玩。demo的地址会在文章最后给出。
1、Android studio工程集成opencv sdk的相关配置
2、图像处理的移植
首先到官网下载Opencv Android版sdk:https://opencv.org/opencv-3-4-1/。下载完成后得到压缩文件opencv-3.4.1-android-sdk.zip,解压之。
新建AndroidStudio项目,通过File -> New -> Import Module导入模块。模块路径选择如下图,选到java这一层即可。
将native->libs文件夹下的内容,拷贝到libs文件夹下。
然后做如下配置:
做完上述配置之后,尝试编译你的app。编译成功证明基本环境已经配置完毕。
opencv对应的java接口基本与c++接口保持一致。将第二章图像处理部分的源码复制过来,改为java语法格式,基本就能使用了。在项目中需要注意的有两点。
OpenCVLoader.initAsync
System.loadLibrary("opencv_java3");
关于图像处理过程的代码,正如上面所说,改成java语法格式就能够使用了,直接上改过之后的源码。
private void alignMat(Mat src) {
Size size = src.size();
Mat gray = new Mat(src.size(), CV_8UC3);
Mat yuv = new Mat(size, CV_8UC3);
Mat faceOutline = Mat.zeros(size, CV_8UC3);
Mat srcClone = src.clone();
Scalar color = new Scalar(255, 255, 0);
Imgproc.cvtColor(src, gray, COLOR_BGR2GRAY);
Imgproc.cvtColor(src, yuv, COLOR_BGR2YCrCb); // 色彩空间转换
//画笑脸开始
double sw = size.width;
double sh = size.height;
int thickness = 4;
double faceH = sh / 2 * 70 / 100;
double faceW = faceH * 72 / 100;
Imgproc.ellipse(faceOutline, new Point(sw / 2, sh / 2), new Size(faceW, faceH), 0.0, 0.0, 360.0, color, thickness, LINE_4 ,0);
double eyeW = faceW * 23 / 100;
double eyeH = faceH * 11 / 100;
double eyeX = faceW * 48 / 100;
double eyeY = faceH * 13 / 100;
Size eyeSize = new Size(eyeW, eyeH);
int eyeA = 15;
int eyeYshift = 11;
Imgproc.ellipse(faceOutline, new Point(sw / 2 - eyeX, sh / 2 - eyeY), eyeSize, 0, 180 + eyeA, 360 - eyeA, color, thickness, LINE_AA, 0);
Imgproc.ellipse(faceOutline, new Point(sw / 2 - eyeX, sh / 2 - eyeY - eyeYshift), eyeSize, 0, 0 + eyeA, 180 - eyeA, color, thickness, LINE_AA, 0);
Imgproc.ellipse(faceOutline, new Point(sw / 2 + eyeX, sh / 2 - eyeY), eyeSize, 0, 180 + eyeA, 360 - eyeA, color, thickness, LINE_AA, 0);
Imgproc.ellipse(faceOutline, new Point(sw / 2 + eyeX, sh / 2 - eyeY - eyeYshift), eyeSize, 0, 0 + eyeA, 180 - eyeA, color, thickness, LINE_AA, 0);
double mouthY = faceH * 48 / 100;
double mouthW = faceW * 45 / 100;
double mouthH = faceH * 6 / 100;
Imgproc.ellipse(faceOutline, new Point(sw / 2, sh / 2 + mouthY), new Size(mouthW, mouthH), 0, 0, 180, color, thickness, LINE_AA, 0);
int fontFace = FONT_HERSHEY_COMPLEX;
float fontScale = 1.0f;
int fontThickness = 2;
String szMsg = "Put your face here";
Imgproc.putText(faceOutline, szMsg, new Point(sw * 23 / 100, sh * 10 / 100), fontFace, fontScale, color, fontThickness, LINE_AA, false);
//画笑脸结束
//中值+拉普拉斯+闭运算进行轮廓提取
Mat mask, maskPlusBorder;
maskPlusBorder = Mat.zeros((int)sh + 2, (int)sw + 2, CV_8UC1);
mask = maskPlusBorder.submat(new Rect(1, 1, (int)sw, (int)sh));
Imgproc.medianBlur(gray, gray, 7);
Mat edges = gray.clone();
Imgproc.Laplacian(gray, edges, CV_8U);
Imgproc.resize(edges, mask, size);
int EDGES_THRESHOLD = 80;
Imgproc.threshold(mask, mask, EDGES_THRESHOLD, 255, THRESH_BINARY);
Imgproc.dilate(mask, mask, new Mat());
Imgproc.erode(mask, mask, new Mat());
//种子像素点
int NUM_SKIN_POINTS = 6;
Point[] skinPts = new Point[NUM_SKIN_POINTS];
skinPts[0] = new Point(sw / 2, sh / 2 - sh / 6);
skinPts[1] = new Point(sw / 2 - sw / 11, sh / 2 - sh / 6);
skinPts[2] = new Point(sw / 2 + sw / 11, sh / 2 - sh / 6);
skinPts[3] = new Point(sw / 2, sh / 2 - sh / 16);
skinPts[4] = new Point(sw / 2 - sw / 9, sh / 2 - sh / 16);
skinPts[5] = new Point(sw / 2 + sw / 9, sh / 2 - sh / 16);
//上下限
int LOWER_Y = 60;
int UPPER_Y = 80;
int LOWER_Cr = 25;
int UPPER_Cr = 15;
int LOWER_Cb = 20;
int UPPER_Cb = 15;
Scalar lowerDiff = new Scalar(LOWER_Y, LOWER_Cr, LOWER_Cb);
Scalar upperDiff = new Scalar(UPPER_Y, UPPER_Cr, UPPER_Cb);
//漫水填充
int CONNECTED_COMPONENTS = 4;
int flags = CONNECTED_COMPONENTS | FLOODFILL_FIXED_RANGE | FLOODFILL_MASK_ONLY;
Mat edgeMask = mask.clone();
for (int i = 0; i < NUM_SKIN_POINTS; i++) {
Imgproc.floodFill(yuv, maskPlusBorder, skinPts[i], new Scalar(255, 255, 255), null, lowerDiff, upperDiff, flags);
}
//最后叠加矩阵
Imgproc.threshold(mask, mask, 0, 255, THRESH_BINARY);
Core.subtract(mask, edgeMask, mask);
int Red = 0;
int Green = 70;
int Blue = 0;
Core.add(src, new Scalar(Red, Green, Blue), src, mask);
}
本章介绍了Mastering Opencv一书第一章所涉及到的知识点,并实现了一个简易的demo。
关于图像处理的流程还有很多能够提升的地方。比如,现在的流程中,漫水填充的六个seed点完全是经验值,而且比例是写死的。这就要求拍照时,必须距离合适,而且是正面照才能得到不错的效果,稍远或者稍近都会导致效果都不好。解决这个问题的方法当然是首先进行图像中的人脸检测,然后根据人脸大小,按比例选取漫水填充的seed。关于人脸检测算法,在后面的章节会有所提及,但这并不是本章的重点。作为本书的第一章,其目的主要还是要告诉读者,opencv其实可以实现许多有意思的功能,并介绍一些图像处理方面的思想。随着对后面章节的学习,对opencv的认识,也将会逐步深入。
最后,贴上本Android工程的源码,仅供借鉴 https://github.com/mymottoissh/Mastering-Opencv-Chapter1。