- 如何將一幅圖像轉(zhuǎn)化為素描圖
- 如何轉(zhuǎn)化一幅畫并疊加素描,轉(zhuǎn)化為漫畫
- 一個可怕的“邪惡”模式,創(chuàng)造壞的角色而不是好的角色。
- 一個基本的皮膚探測器和皮膚顏色改變器,給人綠色的“外星人”皮膚
- 如何將項目從桌面應(yīng)用程序轉(zhuǎn)換為移動應(yīng)用程序
我們想讓真實世界的相機(jī)框架看起來更貼近卡通,基本的想法是讓顏色填充平坦的部分,然后在強(qiáng)邊緣畫粗線。換句話說,就是平坦的區(qū)域應(yīng)該變得更平坦,而邊緣則應(yīng)該變得更明顯。所以進(jìn)行邊緣檢測并使平坦區(qū)域光滑化,然后在頂部繪制增強(qiáng)的邊緣以達(dá)到卡通的效果。
在開發(fā)移動計算機(jī)視覺應(yīng)用程序時,最好先構(gòu)建一個完整的桌面版本,然后將其移植到移動設(shè)備上,因為開發(fā)和調(diào)試桌面程序比移動應(yīng)用程序要容易得多!因此,本章將以一個完整的卡通化桌面程序開始,您可以使用您喜歡的IDE(例如,Visual Studio、XCode、Eclipse、QtCreator等等)來創(chuàng)建它。在桌面正常工作之后,最后一節(jié)將展示如何使用Eclipse將其移植到Android(或者可能是iOS)上。因為我們將創(chuàng)建兩個不同的項目,主要是共享相同的源代碼與不同的圖形用戶界面,你可以創(chuàng)建一個庫,是聯(lián)系在一起的兩個項目,但為簡單起見,我們將把桌面應(yīng)用和Android項目相鄰,并設(shè)置訪問一些文件(cartoon.cpp和cartoon.h,包含所有圖像處理代碼)從桌面文件夾。

這里是用opencv GUI窗口,初始化攝像頭。
要訪問計算機(jī)的網(wǎng)絡(luò)攝像頭或攝像設(shè)備,你可以簡單地在上調(diào)cv::VideoCapture::open() (OpenCV的訪問相機(jī)設(shè)備的方法),并將0作為默認(rèn)的攝像頭ID號。有些計算機(jī)有多個攝像頭,或者它們不能作為默認(rèn)的攝像頭0工作;因此,允許用戶將所需的相機(jī)號作為命令行參數(shù)傳遞給用戶是一種常見的實踐,例如,如果他們想嘗試相機(jī)1、2或-1。我們還將嘗試將相機(jī)分辨率設(shè)置為640×480,使cv::VideoCapture::set(),以便在高分辨率相機(jī)上運行得更快。
根據(jù)您的相機(jī)模型、驅(qū)動程序或系統(tǒng),OpenCV可能不會改變您的相機(jī)的屬性。這對這個項目來說并不重要,所以如果它不能和你的相機(jī)一起使用,不要擔(dān)心。
您可以將此代碼放在main_desktop.cpp的main()函數(shù)中:
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);
在網(wǎng)絡(luò)攝像頭初始化之后,您可以將當(dāng)前的相機(jī)圖像作為cv::Mat對象(OpenCV的圖像容器)抓取。您可以使用cv:::VideoCapture對象中的c++流媒體操作符將每個攝像頭幀抓取到cv::Mat對象中,就像您從控制臺輸入一樣。
Main camera processing loop for a desktop app(桌面應(yīng)用程序的主相機(jī)處理循環(huán))
如果您想使用OpenCV在屏幕上顯示一個GUI窗口,您可以為每個圖像調(diào)用cv:::imshow(),但是您還必須為每個幀調(diào)用cv::waitKey()一次,否則您的窗口根本不會更新!調(diào)用cv: waitKey(0)無限期地等待,直到用戶在窗口中點擊一個鍵,但是一個正數(shù),如waitKey(20)或更高的值,至少要等待這么多毫秒。
可以在main_desktop.cpp中放一個主循環(huán)代碼,作為你實時相機(jī)應(yīng)用的基礎(chǔ):
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(生成一個黑白素描)
為了獲得相機(jī)框架的草圖(黑白圖),我們將使用邊緣檢測過濾器;而為了得到一幅彩色畫,我們將使用一個邊緣保護(hù)過濾器(雙邊濾波器)來進(jìn)一步平滑平坦區(qū)域,同時保持邊緣完整。通過在彩繪上疊加素描,我們得到了最終app截圖之前的動畫效果。
有許多不同的邊緣檢測濾波器,如Sobel, Scharr, Laplacian濾波器,或canny邊緣檢測器。我們將使用拉普拉斯邊緣濾波器,因為與Sobel或Scharr相比自它產(chǎn)生邊緣看起來最接近徒手草圖,與Canny邊緣探測器相比,是相當(dāng)一致的,它能產(chǎn)生非常干凈的線條圖,但更容易受到相機(jī)幀中隨機(jī)噪聲的影響,因此線條圖在幀之間的變化很大。
然而,在使用拉普拉斯邊緣濾波器之前,我們?nèi)匀恍枰獪p少圖像中的噪聲。我們將使用中值濾波器,因為它在去除噪聲的同時保持邊緣清晰;此外,它也不像雙邊過濾器那么慢。由于拉普拉斯邊緣濾波器使用對象為灰度圖,所以我們先應(yīng)該將RGB圖像轉(zhuǎn)換為灰度圖像,在你的空文件cartoon.cpp,把這段代碼放在頂部,這樣您就可以訪問OpenCV和標(biāo)準(zhǔn)c++模板,而無需在每個地方輸入cv::和std::。
// Include OpenCV's C++ Interface
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
把這個和剩下的代碼放在cartoon.cpp文件中的cartoonifyImage()函數(shù)中,
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過濾器會產(chǎn)生不同亮度的邊緣,因此為了使邊緣看起來更像草圖,我們使用了一個二元閾值來使邊緣要么是白色,要么是黑色,
Mat mask;
const int EDGES_THRESHOLD = 80;
threshold(edges, mask, EDGES_THRESHOLD, 255, THRESH_BINARY_INV);
對比初始圖和生成的邊緣蒙版,可以發(fā)現(xiàn)邊緣蒙版看起來更像是草圖,當(dāng)我們后續(xù)生成了彩圖后,可以將此邊緣蒙版覆蓋在上面,用作最終圖樣的黑色線條圖。

Generating a color painting and a cartoon(創(chuàng)作一幅彩色畫和一幅卡通畫)
一個強(qiáng)大的雙邊濾波器可以平滑平坦的區(qū)域,同時保持邊緣的銳利,因此它是一個自動繪圖器或繪畫濾鏡,除了它的速度是非常慢的(也就是說,以秒甚至幾分鐘而不是幾毫秒來衡量)。
因此,我們將使用一些技巧來獲得一個仍然以可接受的速度運行的漂亮的卡通化器。我們可以使用的最重要的技巧是在較低的分辨率下執(zhí)行雙邊過濾。它將產(chǎn)生與完全分辨率類似的效果,但運行速度要快得多。讓我們將像素總數(shù)減少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);
我們不會使用大的雙邊過濾器,而是使用許多小的雙邊過濾器,在更短的時間內(nèi)產(chǎn)生強(qiáng)烈的卡通效果。我們將截斷過濾器(見下圖),而不是執(zhí)行整個過濾器(例如,一個過濾器的大小21 x 21鐘形曲線21像素寬)時,它就使用所需的最小濾波器大小一個令人信服的結(jié)果(例如,使用一個過濾器的大小只是9 x 9即使鐘形曲線是21像素寬)。這個截短的過濾器將應(yīng)用過濾器的主要部分(灰色區(qū)域),而不會將時間浪費在過濾器的次要部分(曲線下的白色區(qū)域),因此它將運行得快幾倍。

我們有四個控制雙邊濾波器的參數(shù):顏色強(qiáng)度、位置強(qiáng)度、尺寸和重復(fù)計數(shù)。由于bilateralFilter()函數(shù)并不能進(jìn)行"就地處理",因此我們需要一個臨時的Mat對象,使用一個過濾器存儲Mat,另一個存儲回輸入。
Mat tmp = Mat(smallSize, CV_8UC3);
int repetitions = 7; // Repetitions for strong cartoon effect.
for (int i=0; i<repetitions; i++) {
int ksize = 9; // Filter size. Has a large effect on speed.
double sigmaColor = 9; // Filter color strength.
double sigmaSpace = 7; // Spatial strength. Affects speed.
bilateralFilter(smallImg, tmp, ksize, sigmaColor, sigmaSpace);
bilateralFilter(tmp, smallImg, ksize, sigmaColor, sigmaSpace);
}
記住,這是應(yīng)用于縮小后的圖像,所以我們需要將圖像擴(kuò)展回原來的大小。然后我們可以覆蓋我們之前找到的邊緣蒙版。為了將邊緣蒙版“素描”覆蓋到雙邊濾波器“繪畫”(下圖左邊)上,我們可以從黑色背景開始,復(fù)制“繪畫”像素,這些像素不是蒙版的邊緣。

Mat bigImg;
resize(smallImg, bigImg, size, 0,0, INTER_LINEAR);
dst.setTo(0);
bigImg.copyTo(dst, mask);
結(jié)果是原始照片的卡通版本,如圖右側(cè)所示,邊緣蒙版被疊加在彩繪上(上圖右邊)。