Cartoonifier and Skin Changer for Android(Android的卡通化器和换肤器)01

  • 如何将一幅图像转化为素描图
  • 如何转化一幅画并叠加素描,转化为漫画
  • 一个可怕的“邪恶”模式,创造坏的角色而不是好的角色。
  • 一个基本的皮肤探测器和皮肤颜色改变器,给人绿色的“外星人”皮肤
  • 如何将项目从桌面应用程序转换为移动应用程序

我们想让真实世界的相机框架看起来更贴近卡通,基本的想法是让颜色填充平坦的部分,然后在强边缘画粗线。换句话说,就是平坦的区域应该变得更平坦,而边缘则应该变得更明显。所以进行边缘检测并使平坦区域光滑化,然后在顶部绘制增强的边缘以达到卡通的效果。

在开发移动计算机视觉应用程序时,最好先构建一个完整的桌面版本,然后将其移植到移动设备上,因为开发和调试桌面程序比移动应用程序要容易得多!因此,本章将以一个完整的卡通化桌面程序开始,您可以使用您喜欢的IDE(例如,Visual Studio、XCode、Eclipse、QtCreator等等)来创建它。在桌面正常工作之后,最后一节将展示如何使用Eclipse将其移植到Android(或者可能是iOS)上。因为我们将创建两个不同的项目,主要是共享相同的源代码与不同的图形用户界面,你可以创建一个库,是联系在一起的两个项目,但为简单起见,我们将把桌面应用和Android项目相邻,并设置访问一些文件(cartoon.cpp和cartoon.h,包含所有图像处理代码)从桌面文件夹。


Cartoonifier and Skin Changer for Android(Android的卡通化器和换肤器)01_第1张图片

这里是用opencv GUI窗口,初始化摄像头。

要访问计算机的网络摄像头或摄像设备,你可以简单地在上调cv::VideoCapture::open() (OpenCV的访问相机设备的方法),并将0作为默认的摄像头ID号。有些计算机有多个摄像头,或者它们不能作为默认的摄像头0工作;因此,允许用户将所需的相机号作为命令行参数传递给用户是一种常见的实践,例如,如果他们想尝试相机1、2或-1。我们还将尝试将相机分辨率设置为640×480,使cv::VideoCapture::set(),以便在高分辨率相机上运行得更快。

根据您的相机模型、驱动程序或系统,OpenCV可能不会改变您的相机的属性。这对这个项目来说并不重要,所以如果它不能和你的相机一起使用,不要担心。

您可以将此代码放在main_desktop.cpp的main()函数中:

int cameraNumber = 0;
if (argc > 1)
cameraNumber = atoi(argv[1]);
// Get access to the camera.
cv::VideoCapture camera;
camera.open(cameraNumber);
if (!camera.isOpened()) {
std::cerr << "ERROR: Could not access the camera or video!" <<
std::endl;
exit(1);
}
// Try to set the camera resolution.
camera.set(cv::CV_CAP_PROP_FRAME_WIDTH, 640);
camera.set(cv::CV_CAP_PROP_FRAME_HEIGHT, 480);

在网络摄像头初始化之后,您可以将当前的相机图像作为cv::Mat对象(OpenCV的图像容器)抓取。您可以使用cv:::VideoCapture对象中的c++流媒体操作符将每个摄像头帧抓取到cv::Mat对象中,就像您从控制台输入一样。

Main camera processing loop for a desktop app(桌面应用程序的主相机处理循环)

如果您想使用OpenCV在屏幕上显示一个GUI窗口,您可以为每个图像调用cv:::imshow(),但是您还必须为每个帧调用cv::waitKey()一次,否则您的窗口根本不会更新!调用cv: waitKey(0)无限期地等待,直到用户在窗口中点击一个键,但是一个正数,如waitKey(20)或更高的值,至少要等待这么多毫秒。

可以在main_desktop.cpp中放一个主循环代码,作为你实时相机应用的基础:

while (true) {
// Grab the next camera frame.
cv::Mat cameraFrame;
camera >> cameraFrame;
if (cameraFrame.empty()) {
std::cerr << "ERROR: Couldn't grab a camera frame." <<
std::endl;
exit(1);
}
// Create a blank output image, that we will draw onto.
cv::Mat displayedFrame(cameraFrame.size(), cv::CV_8UC3);
// Run the cartoonifier filter on the camera frame.
cartoonifyImage(cameraFrame, displayedFrame);
// Display the processed image onto the screen.
imshow("Cartoonifier", displayedFrame);
// IMPORTANT: Wait for at least 20 milliseconds,
// so that the image can be displayed on the screen!
// Also checks if a key was pressed in the GUI window.
// Note that it should be a "char" to support Linux.
char keypress = cv::waitKey(20); // Need this to see anything!
if (keypress == 27) { // Escape Key
// Quit the program!
break;
}
}//end while

Generating a black-and-white sketch(生成一个黑白素描)

为了获得相机框架的草图(黑白图),我们将使用边缘检测过滤器;而为了得到一幅彩色画,我们将使用一个边缘保护过滤器(双边滤波器)来进一步平滑平坦区域,同时保持边缘完整。通过在彩绘上叠加素描,我们得到了最终app截图之前的动画效果。

有许多不同的边缘检测滤波器,如Sobel, Scharr, Laplacian滤波器,或canny边缘检测器。我们将使用拉普拉斯边缘滤波器,因为与Sobel或Scharr相比自它产生边缘看起来最接近徒手草图,与Canny边缘探测器相比,是相当一致的,它能产生非常干净的线条图,但更容易受到相机帧中随机噪声的影响,因此线条图在帧之间的变化很大。

然而,在使用拉普拉斯边缘滤波器之前,我们仍然需要减少图像中的噪声。我们将使用中值滤波器,因为它在去除噪声的同时保持边缘清晰;此外,它也不像双边过滤器那么慢。由于拉普拉斯边缘滤波器使用对象为灰度图,所以我们先应该将RGB图像转换为灰度图像,在你的空文件cartoon.cpp,把这段代码放在顶部,这样您就可以访问OpenCV和标准c++模板,而无需在每个地方输入cv::和std::。

// Include OpenCV's C++ Interface
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;

把这个和剩下的代码放在cartoon.cpp文件中的cartoonifyImage()函数中,

Mat gray;
cvtColor(srcColor, gray, CV_BGR2GRAY);
const int MEDIAN_BLUR_FILTER_SIZE = 7;
medianBlur(gray, gray, MEDIAN_BLUR_FILTER_SIZE);
Mat edges;
const int LAPLACIAN_FILTER_SIZE = 5;
Laplacian(gray, edges, CV_8U, LAPLACIAN_FILTER_SIZE);

Laplacian过滤器会产生不同亮度的边缘,因此为了使边缘看起来更像草图,我们使用了一个二元阈值来使边缘要么是白色,要么是黑色,

Mat mask;
const int EDGES_THRESHOLD = 80;
threshold(edges, mask, EDGES_THRESHOLD, 255, THRESH_BINARY_INV);

对比初始图和生成的边缘蒙版,可以发现边缘蒙版看起来更像是草图,当我们后续生成了彩图后,可以将此边缘蒙版覆盖在上面,用作最终图样的黑色线条图。


Cartoonifier and Skin Changer for Android(Android的卡通化器和换肤器)01_第2张图片
初始图与边缘对比

Generating a color painting and a cartoon(创作一幅彩色画和一幅卡通画)

一个强大的双边滤波器可以平滑平坦的区域,同时保持边缘的锐利,因此它是一个自动绘图器或绘画滤镜,除了它的速度是非常慢的(也就是说,以秒甚至几分钟而不是几毫秒来衡量)。
因此,我们将使用一些技巧来获得一个仍然以可接受的速度运行的漂亮的卡通化器。我们可以使用的最重要的技巧是在较低的分辨率下执行双边过滤。它将产生与完全分辨率类似的效果,但运行速度要快得多。让我们将像素总数减少4倍(例如,半宽半高)。

Size size = srcColor.size();
Size smallSize;
smallSize.width = size.width/2;
smallSize.height = size.height/2;
Mat smallImg = Mat(smallSize, CV_8UC3);
resize(srcColor, smallImg, smallSize, 0,0, INTER_LINEAR);

我们不会使用大的双边过滤器,而是使用许多小的双边过滤器,在更短的时间内产生强烈的卡通效果。我们将截断过滤器(见下图),而不是执行整个过滤器(例如,一个过滤器的大小21 x 21钟形曲线21像素宽)时,它就使用所需的最小滤波器大小一个令人信服的结果(例如,使用一个过滤器的大小只是9 x 9即使钟形曲线是21像素宽)。这个截短的过滤器将应用过滤器的主要部分(灰色区域),而不会将时间浪费在过滤器的次要部分(曲线下的白色区域),因此它将运行得快几倍。


Cartoonifier and Skin Changer for Android(Android的卡通化器和换肤器)01_第3张图片

我们有四个控制双边滤波器的参数:颜色强度、位置强度、尺寸和重复计数。由于bilateralFilter()函数并不能进行"就地处理",因此我们需要一个临时的Mat对象,使用一个过滤器存储Mat,另一个存储回输入。

Mat tmp = Mat(smallSize, CV_8UC3);
int repetitions = 7; // Repetitions for strong cartoon effect.
for (int i=0; i

记住,这是应用于缩小后的图像,所以我们需要将图像扩展回原来的大小。然后我们可以覆盖我们之前找到的边缘蒙版。为了将边缘蒙版“素描”覆盖到双边滤波器“绘画”(下图左边)上,我们可以从黑色背景开始,复制“绘画”像素,这些像素不是蒙版的边缘。


Cartoonifier and Skin Changer for Android(Android的卡通化器和换肤器)01_第4张图片
Mat bigImg;
resize(smallImg, bigImg, size, 0,0, INTER_LINEAR);
dst.setTo(0);
bigImg.copyTo(dst, mask);

结果是原始照片的卡通版本,如图右侧所示,边缘蒙版被叠加在彩绘上(上图右边)。

你可能感兴趣的:(Cartoonifier and Skin Changer for Android(Android的卡通化器和换肤器)01)