596 lines
26 KiB
C++
596 lines
26 KiB
C++
#include "time_helper.h"
|
|
#include "window_helper.h"
|
|
#include "cartesian_coordinate_system_helper.h"
|
|
#include "common_utils.h"
|
|
#include "dlib_detector.h"
|
|
#include "style_transfer.h"
|
|
#include "imgwarp_mls_similarity.h"
|
|
#include "imgwarp_mls_rigid.h"
|
|
#include "imgwarp_piecewiseaffine.h"
|
|
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <filesystem>
|
|
#include <algorithm>
|
|
#include <opencv2/core.hpp>
|
|
|
|
|
|
|
|
|
|
// ############################################################################
|
|
// # VIDEO STYLIZATION #
|
|
// ############################################################################
|
|
bool getStylizedVideo(const std::string& rawPath, const std::string& drawablePath, const std::string& targetPath, const std::string& targetName, const std::string& styleName, const bool stylizeBG, const int NNF_patchsize)
|
|
{
|
|
|
|
std::string styleNameWithoutExtensionAndPrefix = styleName.substr(0, styleName.find_last_of(".")).replace(styleName.find("style_"), 6, "");
|
|
std::string targetNameWithoutExtension = targetName.substr(0, targetName.find_last_of("."));
|
|
|
|
std::string outputName = targetNameWithoutExtension + "_" + styleNameWithoutExtensionAndPrefix + "_"
|
|
+ (NNF_patchsize > 0 ? std::to_string(NNF_patchsize) + "x" + std::to_string(NNF_patchsize) + "_voting" : "0_voting")
|
|
+ (stylizeBG ? "_bg" : "")
|
|
+ ".mp4"; // output file name with extension
|
|
|
|
// --- READ STYLE FILES ---------------------------
|
|
cv::Mat styleImg = cv::imread(drawablePath + styleName);
|
|
if (styleImg.empty()) {
|
|
Log_e("FACEBLIT", "Failed to read style image '" + drawablePath + styleName + "'");
|
|
return false;
|
|
}
|
|
std::ifstream styleLandmarkFile(rawPath + "lm_" + styleNameWithoutExtensionAndPrefix + ".txt");
|
|
std::string styleLandmarkStr((std::istreambuf_iterator<char>(styleLandmarkFile)), std::istreambuf_iterator<char>());
|
|
styleLandmarkFile.close();
|
|
std::vector<cv::Point2i> styleLandmarks = getLandmarkPointsFromString(styleLandmarkStr.c_str());
|
|
cv::Mat lookUpCube = loadLookUpCube(rawPath + "lut_" + styleNameWithoutExtensionAndPrefix + ".bytes");
|
|
|
|
// --- GENERATE STYLE GUIDING CHANNELS ------------
|
|
cv::Mat stylePosGuide = getGradient(styleImg.cols, styleImg.rows, false); // G_pos
|
|
cv::Mat styleAppGuide = getAppGuide(styleImg, true); // G_app
|
|
|
|
if (stylizeBG) {
|
|
// Add 2 landmarks into the bottom corners - to prevent the body moving during MLS deformation
|
|
styleLandmarks.push_back(cv::Point2i(0, styleImg.rows)); // left bottom corner
|
|
styleLandmarks.push_back(cv::Point2i(styleImg.cols, styleImg.rows)); // right bottom corner
|
|
}
|
|
|
|
// Open a video file
|
|
cv::VideoCapture cap(targetPath + targetName);
|
|
if (!cap.isOpened()) {
|
|
Log_e("FACEBLIT", "Unable to open the input video.");
|
|
return false;
|
|
}
|
|
|
|
// Get the width/height and the FPS of the video
|
|
int width = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_WIDTH));
|
|
int height = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_HEIGHT));
|
|
double FPS = cap.get(cv::CAP_PROP_FPS) / 2;
|
|
|
|
// Open a video file for writing (the MP4V codec works on OS X and Windows)
|
|
cv::VideoWriter out("TESTS\\" + outputName, cv::VideoWriter::fourcc('m', 'p', '4', 'v'), FPS, cv::Size(styleImg.cols, styleImg.rows));
|
|
if (!out.isOpened()) {
|
|
Log_e("FACEBLIT", "Unable to open the output video.");
|
|
return false;
|
|
}
|
|
|
|
// Load facemark models
|
|
DlibDetector faceDetector("facemark_models\\shape_predictor_68_face_landmarks.dat");
|
|
std::pair<cv::Rect, std::vector<cv::Point2i>> faceDetResult; // face and its landmarks
|
|
std::vector<cv::Point2i> targetLandmarks;
|
|
|
|
Log_i("FACEBLIT", "Video stylization in progress...");
|
|
|
|
cv::Mat frame;
|
|
int i = 0, j = 0;
|
|
while (true)
|
|
{
|
|
cap >> frame;
|
|
if (frame.empty())
|
|
{
|
|
Log_i("FACEBLIT", "Done."); // no more frames
|
|
break;
|
|
}
|
|
|
|
if (i % 2 == 1) // skip odd frames when we want the half FPS (not in case of target12!!!)
|
|
{
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
frame = frame(cv::Rect(240, 0, width - 480, height)); // crop to fit 4:3 ratio
|
|
cv::resize(frame, frame, cv::Size(styleImg.rows, styleImg.cols)); // resize to fit style size
|
|
*/
|
|
|
|
bool success = faceDetector.detectFacemarks(frame, faceDetResult);
|
|
if (!success)
|
|
{
|
|
Log_i("FACEBLIT", "Face detection failed, continue to the next frame.");
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
targetLandmarks = faceDetResult.second;
|
|
|
|
/* // Load landmarks from file
|
|
std::string lmPath = root + "\\landmarks\\" + videoName.substr(0, videoName.find_last_of(".")) + "\\" + padLeft(std::to_string(i), 4, '0') + ".txt";
|
|
std::ifstream targetLandmarkFile(lmPath);
|
|
if (targetLandmarkFile.fail()) {
|
|
std::cout << "No landmarks for this video are available." << std::endl;
|
|
system("pause");
|
|
return 1;
|
|
}
|
|
std::string targetLandmarkStr((std::istreambuf_iterator<char>(targetLandmarkFile)), std::istreambuf_iterator<char>());
|
|
targetLandmarkFile.close();
|
|
targetLandmarks = getLandmarkPointsFromString(targetLandmarkStr.c_str());
|
|
*/
|
|
|
|
|
|
//alignTargetToStyle(frame, targetLandmarks, styleLandmarks);
|
|
|
|
|
|
if (stylizeBG) {
|
|
// Add 2 landmarks into the bottom corners - to prevent the body moving during MLS deformation
|
|
targetLandmarks.push_back(cv::Point2i(0, frame.rows)); // left bottom corner
|
|
targetLandmarks.push_back(cv::Point2i(frame.cols, frame.rows)); // right bottom corner
|
|
}
|
|
|
|
|
|
// --- GENERATE TARGET GUIDING CHANNELS -------------------------
|
|
cv::Mat targetPosGuide = MLSDeformation(stylePosGuide, styleLandmarks, targetLandmarks); // G_pos
|
|
cv::Mat targetAppGuide = getAppGuide(frame, false); // G_app
|
|
targetAppGuide = grayHistMatching(targetAppGuide, styleAppGuide);
|
|
|
|
// --- STYLE TRANSFER --------------------------------------------
|
|
cv::Rect2i headAreaRect = CartesianCoordinateSystem::getHeadAreaRect(targetLandmarks, cv::Size(frame.cols, frame.rows));
|
|
|
|
cv::Mat stylizedImg, stylizedImgNoApp;
|
|
if (NNF_patchsize > 0)
|
|
{
|
|
stylizedImg = styleBlit_voting(stylePosGuide, targetPosGuide, styleAppGuide, targetAppGuide, lookUpCube, styleImg, headAreaRect, NNF_patchsize);
|
|
if (stylizeBG)
|
|
stylizedImgNoApp = styleBlit_voting(stylePosGuide, targetPosGuide, cv::Mat(), cv::Mat(), lookUpCube, styleImg, cv::Rect2i(0, 0, frame.cols, frame.rows), NNF_patchsize, 10, 10, 0);
|
|
}
|
|
else
|
|
{
|
|
stylizedImg = styleBlit(stylePosGuide, targetPosGuide, styleAppGuide, targetAppGuide, lookUpCube, styleImg, headAreaRect);
|
|
if (stylizeBG)
|
|
stylizedImgNoApp = styleBlit(stylePosGuide, targetPosGuide, cv::Mat(), cv::Mat(), lookUpCube, styleImg, cv::Rect2i(0, 0, frame.cols, frame.rows), 10, 10, 0);
|
|
}
|
|
|
|
// --- FACE MASK -----------------------------------
|
|
cv::Mat targetFaceMask = getSkinMask(frame, targetLandmarks);
|
|
|
|
// --- ALPHA BLEND ---------------------------------
|
|
cv::Mat alphaBlendResult;
|
|
if (stylizeBG)
|
|
alphaBlendResult = alphaBlendFG_BG(stylizedImg, stylizedImgNoApp, targetFaceMask, 25.0); // blend the face into the stylized image without appearance
|
|
else
|
|
alphaBlendResult = alphaBlendFG_BG(stylizedImg, frame, targetFaceMask, 25.0); // blend the face into the original target image
|
|
|
|
Window::imgShow("Result", alphaBlendResult);
|
|
|
|
// Save frame to video
|
|
out << alphaBlendResult;
|
|
|
|
i++;
|
|
j++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// ############################################################################
|
|
// # SINGLE IMAGE STYLIZATION #
|
|
// ############################################################################
|
|
bool getStylizedImage(const std::string& rawPath, const std::string& drawablePath, const std::string& targetPath, const std::string& targetName, const std::string& styleName, const bool stylizeBG, const int NNF_patchsize)
|
|
{
|
|
|
|
std::string styleNameWithoutExtensionAndPrefix = styleName.substr(0, styleName.find_last_of(".")).replace(styleName.find("style_"), 6, "");
|
|
std::string targetNameWithoutExtension = targetName.substr(0, targetName.find_last_of("."));
|
|
|
|
std::string outputName = targetNameWithoutExtension + "_" + styleNameWithoutExtensionAndPrefix + "_"
|
|
+ (NNF_patchsize > 0 ? std::to_string(NNF_patchsize) + "x" + std::to_string(NNF_patchsize) + "_voting" : "0_voting")
|
|
+ (stylizeBG ? "_bg" : "")
|
|
+ ".png"; // output file name with extension
|
|
|
|
// --- READ STYLE FILES -----------------
|
|
cv::Mat styleImg = cv::imread(drawablePath + styleName);
|
|
if (styleImg.empty()) {
|
|
Log_e("FACEBLIT", "Failed to read style image '" + drawablePath + styleName + "'");
|
|
return false;
|
|
}
|
|
std::ifstream styleLandmarkFile(rawPath + "lm_" + styleNameWithoutExtensionAndPrefix + ".txt");
|
|
std::string styleLandmarkStr((std::istreambuf_iterator<char>(styleLandmarkFile)), std::istreambuf_iterator<char>());
|
|
styleLandmarkFile.close();
|
|
std::vector<cv::Point2i> styleLandmarks = getLandmarkPointsFromString(styleLandmarkStr.c_str());
|
|
cv::Mat lookUpCube = loadLookUpCube(rawPath + "lut_" + styleNameWithoutExtensionAndPrefix + ".bytes");
|
|
|
|
// --- DETECT LANDMARKS IN TARGET ----------------
|
|
cv::Mat targetImg = cv::imread(targetPath + targetName);
|
|
if (styleImg.empty()) {
|
|
Log_e("FACEBLIT", "Failed to read target image '" + targetPath + targetName + "'");
|
|
return false;
|
|
}
|
|
std::vector<cv::Point2i> targetLandmarks;
|
|
std::pair<cv::Rect, std::vector<cv::Point2i>> detectionResult; // face rectangle and landmarks
|
|
DlibDetector dlibDetector("facemark_models\\shape_predictor_68_face_landmarks.dat"); // load model
|
|
|
|
TimeMeasure t_detection;
|
|
bool success = dlibDetector.detectFacemarks(targetImg, detectionResult);
|
|
if (!success)
|
|
{
|
|
Log_e("FACEBLIT", "Face detection failed.");
|
|
return false;
|
|
}
|
|
Log_i("FACEBLIT", "LM detection time: " + std::to_string(t_detection.elapsed_milliseconds()) + " ms");
|
|
|
|
targetLandmarks = detectionResult.second;
|
|
//CartesianCoordinateSystem::drawLandmarks(targetImg, targetLandmarks);
|
|
//cv::imwrite("TESTS\\lm.png", targetImg);
|
|
|
|
// --- GENERATE GUIDING CHANNELS ------------------
|
|
cv::Mat stylePosGuide = getGradient(targetImg.cols, targetImg.rows, false); // style's G_pos
|
|
|
|
TimeMeasure t_targetPosGuide;
|
|
cv::Mat targetPosGuide = MLSDeformation(stylePosGuide, styleLandmarks, targetLandmarks); // target's G_pos
|
|
Log_i("FACEBLIT", "T G_pos time: " + std::to_string(t_targetPosGuide.elapsed_milliseconds()) + " ms");
|
|
|
|
TimeMeasure t_styleAppGuide;
|
|
cv::Mat styleAppGuide = getAppGuide(styleImg, true); // style's G_app
|
|
Log_i("FACEBLIT", "S G_app time: " + std::to_string(t_styleAppGuide.elapsed_milliseconds()) + " ms");
|
|
|
|
TimeMeasure t_targetAppGuide;
|
|
cv::Mat targetAppGuide = getAppGuide(targetImg, false); // target's G_app
|
|
Log_i("FACEBLIT", "T G_app time: " + std::to_string(t_targetAppGuide.elapsed_milliseconds()) + " ms");
|
|
|
|
TimeMeasure t_targetAppGuide_hist;
|
|
targetAppGuide = grayHistMatching(targetAppGuide, styleAppGuide);
|
|
Log_i("FACEBLIT", "T G_app hist. matching time: " + std::to_string(t_targetAppGuide_hist.elapsed_milliseconds()) + " ms");
|
|
|
|
// --- GENERATE LOOKUP TABLE FOR A NEW STYLE OR LOWER RESOLUTION -----------
|
|
/* // Generate style resources for lower resolution
|
|
cv::resize(styleImg, styleImg, cv::Size(480, 640));
|
|
cv::Mat styleGpos = getGradient(480, 640, false); // G_pos
|
|
cv::Mat styleGapp = getAppGuide(styleImg, true); // G_app
|
|
cv::imwrite(drawablePath + "style_" + styleNameWithoutExtensionAndPrefix + "_480x640.png", styleImg);
|
|
for (int i = 0; i < styleLandmarks.size(); i++)
|
|
styleLandmarks[i] *= 0.625;
|
|
CartesianCoordinateSystem::drawLandmarks(styleImg, styleLandmarks);
|
|
Window::imgShow("lm", styleImg);
|
|
CartesianCoordinateSystem::savePointsIntoFile(styleLandmarks, rawPath + "lm_" + styleNameWithoutExtensionAndPrefix + "_480x640.txt");
|
|
|
|
TimeMeasure t_lut;
|
|
lookUpCube = getLookUpCube(styleGpos, styleGapp); // 3D table that maps Pos_Red, Pos_Green, App values to coordinates u, v in style
|
|
Log_i("FACEBLIT", "Lookup table time: " + std::to_string(t_lut.elapsed_milliseconds()) + " ms");
|
|
saveLookUpCube(lookUpCube, rawPath + "lut_" + styleNameWithoutExtensionAndPrefix + "_480x640.bytes");
|
|
*/
|
|
|
|
// --- STYLE TRANSFER --------------------------------------------
|
|
cv::Rect2i headAreaRect = CartesianCoordinateSystem::getHeadAreaRect(targetLandmarks, cv::Size(targetImg.cols, targetImg.rows));
|
|
|
|
TimeMeasure t_styleBlit;
|
|
cv::Mat stylizedImg, stylizedImgNoApp;
|
|
if (NNF_patchsize > 0)
|
|
{
|
|
stylizedImg = styleBlit_voting(stylePosGuide, targetPosGuide, styleAppGuide, targetAppGuide, lookUpCube, styleImg, headAreaRect, NNF_patchsize);
|
|
if (stylizeBG)
|
|
stylizedImgNoApp = styleBlit_voting(stylePosGuide, targetPosGuide, cv::Mat(), cv::Mat(), lookUpCube, styleImg, cv::Rect2i(0, 0, targetImg.cols, targetImg.rows), NNF_patchsize, 10, 10, 0);
|
|
}
|
|
else
|
|
{
|
|
stylizedImg = styleBlit(stylePosGuide, targetPosGuide, styleAppGuide, targetAppGuide, lookUpCube, styleImg, headAreaRect);
|
|
if (stylizeBG)
|
|
stylizedImgNoApp = styleBlit(stylePosGuide, targetPosGuide, cv::Mat(), cv::Mat(), lookUpCube, styleImg, cv::Rect2i(0, 0, targetImg.cols, targetImg.rows), 10, 10, 0);
|
|
}
|
|
Log_i("FACEBLIT", "Style transfer time: " + std::to_string(t_styleBlit.elapsed_milliseconds()) + " ms");
|
|
|
|
// --- FACE MASK -----------------------------------
|
|
TimeMeasure timer;
|
|
cv::Mat targetFaceMask = getSkinMask(targetImg, targetLandmarks);
|
|
Log_i("FACEBLIT", "T face mask time: " + std::to_string(timer.elapsed_milliseconds()) + " ms");
|
|
timer.reset();
|
|
|
|
// --- ALPHA BLEND ---------------------------------
|
|
TimeMeasure t_blend;
|
|
cv::Mat alphaBlendResult;
|
|
if (stylizeBG)
|
|
alphaBlendResult = alphaBlendFG_BG(stylizedImg, stylizedImgNoApp, targetFaceMask, 25.0); // blend the face into the stylized image without appearance
|
|
else
|
|
alphaBlendResult = alphaBlendFG_BG(stylizedImg, targetImg, targetFaceMask, 25.0); // blend the face into the original target image
|
|
Log_i("FACEBLIT", "Alpha blend time: " + std::to_string(t_blend.elapsed_milliseconds()) + " ms");
|
|
|
|
Window::imgShow("Result", alphaBlendResult);
|
|
cv::imwrite("TESTS\\" + outputName, alphaBlendResult);
|
|
|
|
return true;
|
|
}
|
|
|
|
// ##################################################################################
|
|
// # Add new style and create its resources (lookup table, landmarks) #
|
|
// ##################################################################################
|
|
|
|
bool addNewStyle(const std::string& inputPath, const std::string& rawPath = "..\\app\\src\\main\\res\\raw\\", const std::string& drawablePath = "..\\app\\src\\main\\res\\drawable\\")
|
|
{
|
|
// Load dlib model for landmark detection
|
|
std::pair<cv::Rect, std::vector<cv::Point2i>> detectionResult; // face rectangle and landmarks
|
|
DlibDetector dlibDetector("facemark_models\\shape_predictor_68_face_landmarks.dat"); // load model
|
|
|
|
// Get a style name
|
|
int startIdx = inputPath.find_last_of("\\") != std::string::npos ? inputPath.find_last_of("\\") + 1 : 0;
|
|
std::string styleNameWithoutExtension = inputPath.substr(startIdx, inputPath.find_last_of(".") - startIdx);
|
|
styleNameWithoutExtension.erase(std::remove(styleNameWithoutExtension.begin(), styleNameWithoutExtension.end(), '_'), styleNameWithoutExtension.end());
|
|
styleNameWithoutExtension.erase(std::remove(styleNameWithoutExtension.begin(), styleNameWithoutExtension.end(), '-'), styleNameWithoutExtension.end());
|
|
styleNameWithoutExtension.erase(std::remove(styleNameWithoutExtension.begin(), styleNameWithoutExtension.end(), ' '), styleNameWithoutExtension.end());
|
|
std::transform(styleNameWithoutExtension.begin(), styleNameWithoutExtension.end(), styleNameWithoutExtension.begin(), ::tolower);
|
|
|
|
// --- Original resolution for the desktop testing ----------------------------------------
|
|
cv::Mat styleImg = cv::imread(inputPath);
|
|
if (styleImg.empty()) {
|
|
Log_e("FACEBLIT", "Failed to read style image '" + inputPath + "'");
|
|
return false;
|
|
}
|
|
if (styleImg.cols != 768 || styleImg.rows != 1024) {
|
|
Log_e("FACEBLIT", "Wrong resolution! Please provide the image in resolution 768x1024 (width x height).");
|
|
return false;
|
|
}
|
|
cv::imwrite(drawablePath + "style_" + styleNameWithoutExtension + ".png", styleImg);
|
|
cv::Mat styleGpos = getGradient(styleImg.cols, styleImg.rows, false); // G_pos
|
|
cv::Mat styleGapp = getAppGuide(styleImg, true); // G_app
|
|
|
|
Log_i("FACEBLIT", "Generating lookup table for original resolution...this may take a while.");
|
|
cv::Mat lookUpCube = getLookUpCube(styleGpos, styleGapp); // 3D table that maps Pos_Red, Pos_Green, App values to coordinates u, v in style
|
|
saveLookUpCube(lookUpCube, rawPath + "lut_" + styleNameWithoutExtension + ".bytes");
|
|
|
|
bool success = dlibDetector.detectFacemarks(styleImg, detectionResult);
|
|
if (!success) {
|
|
Log_e("FACEBLIT", "Landmark detection failed.");
|
|
return false;
|
|
}
|
|
std::vector<cv::Point2i> landmarks = detectionResult.second;
|
|
cv::Mat copyImg = styleImg.clone();
|
|
CartesianCoordinateSystem::drawLandmarks(copyImg, landmarks);
|
|
//Window::imgShow("landmarks", copyImg);
|
|
cv::imwrite("TESTS\\lm_" + styleNameWithoutExtension + ".png", copyImg);
|
|
//Log_i("FACEBLIT", "Style image with drawn landmarks was saved to 'VS\\TESTS\\lm_" + styleNameWithoutExtension + ".png'.");
|
|
|
|
CartesianCoordinateSystem::savePointsIntoFile(landmarks, rawPath + "lm_" + styleNameWithoutExtension + ".txt");
|
|
|
|
// --- Lower resolution for the Android app ------------------------------------------------
|
|
cv::resize(styleImg, styleImg, cv::Size(480, 640));
|
|
cv::imwrite(drawablePath + "style_" + styleNameWithoutExtension + "_480x640.png", styleImg);
|
|
styleGpos = getGradient(styleImg.cols, styleImg.rows, false); // G_pos
|
|
styleGapp = getAppGuide(styleImg, true); // G_app
|
|
|
|
Log_i("FACEBLIT", "Generating lookup table for lower resolution...this may take a while.");
|
|
lookUpCube = getLookUpCube(styleGpos, styleGapp); // 3D table that maps Pos_Red, Pos_Green, App values to coordinates u, v in style
|
|
saveLookUpCube(lookUpCube, rawPath + "lut_" + styleNameWithoutExtension + "_480x640.bytes");
|
|
|
|
for (int i = 0; i < landmarks.size(); i++)
|
|
landmarks[i] *= 0.625;
|
|
//copyImg = styleImg.clone();
|
|
//CartesianCoordinateSystem::drawLandmarks(copyImg, landmarks);
|
|
//Window::imgShow("landmarks", copyImg);
|
|
|
|
CartesianCoordinateSystem::savePointsIntoFile(landmarks, rawPath + "lm_" + styleNameWithoutExtension + "_480x640.txt");
|
|
|
|
// --- Save thumbnail for Android app -------------------------------------------------------
|
|
cv::resize(styleImg, styleImg, cv::Size(300, 400));
|
|
cv::imwrite(drawablePath + "recycler_view_" + styleNameWithoutExtension + ".jpg", styleImg);
|
|
|
|
Log_i("FACEBLIT", "--------------------------------------------------------------");
|
|
Log_i("FACEBLIT", "Style's landmarks have to be as precise as possible!");
|
|
Log_i("FACEBLIT", "Double check landmark positions in 'VS\\TESTS\\lm_" + styleNameWithoutExtension + ".png' and if necessary, fix them manually in corresponding TXT files:");
|
|
Log_i("FACEBLIT", "app\\src\\main\\res\\raw\\lm_" + styleNameWithoutExtension + ".txt");
|
|
Log_i("FACEBLIT", "app\\src\\main\\res\\raw\\lm_" + styleNameWithoutExtension + "_480x640.txt");
|
|
Log_i("FACEBLIT", "HINT: open the 'VS\\TESTS\\lm_" + styleNameWithoutExtension + ".png' in MS Paint, where you can see the coordinates of your cursor and check 'docs\\landmarks.png' to see the order of landmarks.");
|
|
Log_i("FACEBLIT", "--------------------------------------------------------------");
|
|
Log_i("FACEBLIT", "Finally, copy the following code into the switch block of ResourceHelper.java file:");
|
|
std::cout << std::endl;
|
|
std::cout << "\tcase \"" << styleNameWithoutExtension << "\":" << std::endl;
|
|
std::cout << "\t\tid_img = R.drawable.style_" << styleNameWithoutExtension << "_480x640;" << std::endl;
|
|
std::cout << "\t\tid_lm = R.raw.lm_" << styleNameWithoutExtension << "_480x640;" << std::endl;
|
|
std::cout << "\t\tid_lut = R.raw.lut_" << styleNameWithoutExtension << "_480x640;" << std::endl;
|
|
std::cout << "\t\tbreak;" << std::endl;
|
|
std::cout << std::endl;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
const std::string rawPath = "..\\app\\src\\main\\res\\raw\\"; // path to styles landmarks and lookup tables
|
|
const std::string drawablePath = "..\\app\\src\\main\\res\\drawable\\"; // path to style exemplars
|
|
|
|
// *** PARAMETERS TO SET ***********************************************************************************************************
|
|
const std::string targetPath = "TESTS\\"; // path to target images and videos
|
|
const std::string targetName = "target2.png"; // name of a target PNG image or MP4 video with extension from drawablePath
|
|
const std::string styleName = "style_watercolorgirl.png"; // name of a style PNG with extension from drawablePath
|
|
const bool stylizeBG = false; // true - stylized face with appearace is blended into the stylized target without appearance, false - stylized face is blended into the original target
|
|
const int NNF_patchsize = 3; // voting patch size (0 for no voting), ideal is 3 or 5; it has to be an ODD NUMBER!!!
|
|
// *********************************************************************************************************************************
|
|
|
|
bool success = false;
|
|
|
|
|
|
// ADD A NEW STYLE (DETECT LANDMARKS & GENERATE LOOKUP TABLE)
|
|
/*
|
|
success = addNewStyle("C:\\Users\\Aneta\\Pictures\\styles\\abstract.png");
|
|
if (!success) {
|
|
Log_e("FACEBLIT", "Adding of the new style failed.");
|
|
system("pause");
|
|
return -1;
|
|
}
|
|
else {
|
|
Log_i("FACEBLIT", "The new style added successfully.");
|
|
system("pause");
|
|
return 0;
|
|
}
|
|
*/
|
|
|
|
|
|
if (targetName.find(".png") != std::string::npos)
|
|
success = getStylizedImage(rawPath, drawablePath, targetPath, targetName, styleName, stylizeBG, NNF_patchsize); // Result is saved into "VS/TESTS"
|
|
|
|
if (targetName.find(".mp4") != std::string::npos)
|
|
success = getStylizedVideo(rawPath, drawablePath, targetPath, targetName, styleName, stylizeBG, NNF_patchsize); // Result is saved into "VS/TESTS"
|
|
|
|
if (!success) {
|
|
Log_e("FACEBLIT", "Stylization failed.");
|
|
system("pause");
|
|
return -1;
|
|
}
|
|
|
|
Log_i("FACEBLIT", "Stylization succeeded. See the result in 'VS/TESTS'");
|
|
system("pause");
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cv::Mat alphaBlendTransparentBG(cv::Mat foreground, cv::Mat alphaMask)
|
|
{
|
|
cv::Mat background(foreground.rows, foreground.cols, CV_32FC3, cv::Scalar(0, 0, 0));
|
|
|
|
// Convert Mats to float data type
|
|
foreground.convertTo(foreground, CV_32FC3, 1.0 / 255.0);
|
|
alphaMask.convertTo(alphaMask, CV_32FC3, 1.0 / 255.0);
|
|
|
|
blur(alphaMask, alphaMask, cv::Size(20, 20)); // Box blur
|
|
|
|
multiply(alphaMask, foreground, foreground); // Multiply the foreground with the alpha matte
|
|
multiply(cv::Scalar::all(1.0) - alphaMask, background, background); // Multiply the background with ( 1 - alpha )
|
|
|
|
add(foreground, background, background);
|
|
|
|
std::vector<cv::Mat> bgChannels;
|
|
cv::split(background, bgChannels);
|
|
|
|
std::vector<cv::Mat> alpChannels;
|
|
cv::split(alphaMask, alpChannels);
|
|
|
|
bgChannels.push_back(alpChannels.at(0)); // create alpha channel
|
|
|
|
Mat result;
|
|
cv::merge(bgChannels, result);
|
|
|
|
result.convertTo(result, CV_8UC4, 255.0);
|
|
|
|
return result;
|
|
}
|
|
|
|
// Creates diff image of Laplacian pyramid
|
|
cv::Mat createDiff(const cv::Mat& original, const cv::Mat& filtered)
|
|
{
|
|
// Must have the same rows:cols ratio
|
|
if ((float)original.rows / (float)filtered.rows != (float)original.cols / (float)filtered.cols)
|
|
{
|
|
std::cout << original.size << ", " << filtered.size << std::endl;
|
|
throw std::runtime_error("@original and @filtered must have the same rows:cols ratio");
|
|
}
|
|
|
|
cv::Mat filtered_deep_copy;
|
|
cv::resize(filtered, filtered_deep_copy, cv::Size(original.cols, original.rows), 0, 0, cv::INTER_LINEAR);
|
|
|
|
cv::Mat diff(original.rows, original.cols, CV_8UC3);
|
|
for (int row = 0; row < original.rows; row++)
|
|
{
|
|
for (int col = 0; col < original.cols; col++)
|
|
{
|
|
int B = original.at<cv::Vec3b>(row, col)[0] - filtered_deep_copy.at<cv::Vec3b>(row, col)[0];
|
|
int G = original.at<cv::Vec3b>(row, col)[1] - filtered_deep_copy.at<cv::Vec3b>(row, col)[1];
|
|
int R = original.at<cv::Vec3b>(row, col)[2] - filtered_deep_copy.at<cv::Vec3b>(row, col)[2];
|
|
|
|
diff.at<cv::Vec3b>(row, col) = cv::Vec3b((B / 2) + 128, (G / 2) + 128, (R / 2) + 128);
|
|
}
|
|
}
|
|
return diff;
|
|
}
|
|
|
|
void createInputImages_LVLS2(const cv::Mat& style, cv::Mat& style_base_only, cv::Mat& style_diff_only)
|
|
{
|
|
cv::pyrDown(style, style_base_only, cv::Size(style.cols / 2, style.rows / 2));
|
|
cv::pyrDown(style_base_only, style_base_only, cv::Size(style_base_only.cols / 2, style_base_only.rows / 2));
|
|
cv::pyrDown(style_base_only, style_base_only, cv::Size(style_base_only.cols / 2, style_base_only.rows / 2));
|
|
|
|
/*cv::bilateralFilter(style, style_base_only, 9, 31, 31);
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
cv::Mat tmp(style_base_only.rows, style_base_only.cols, CV_8UC3);
|
|
cv::bilateralFilter(style_base_only, tmp, 9, 31, 31);
|
|
tmp.copyTo(style_base_only);
|
|
}*/
|
|
|
|
style_diff_only = createDiff(style, style_base_only);
|
|
|
|
if (style.size != style_base_only.size)
|
|
{
|
|
cv::resize(style_base_only, style_base_only, cv::Size(style.cols, style.rows), 0, 0, cv::INTER_LINEAR);
|
|
}
|
|
|
|
/*imshowInWindow("style", style);
|
|
imshowInWindow("style_base_only", style_base_only);
|
|
imshowInWindow("style_diff_only", style_diff_only);
|
|
|
|
cv::Mat reconstructed_only = reconstructDiff(style_base_only, style_diff_only);
|
|
|
|
imshowInWindow("reconstructed_only", reconstructed_only);
|
|
|
|
cv::waitKey(0);*/
|
|
}
|
|
|
|
cv::Mat cropWidth(const cv::Mat& image, const int dstWidth, const int centerX)
|
|
{
|
|
int x = std::max(0, centerX - dstWidth / 2);
|
|
if (x + dstWidth > image.cols)
|
|
x = image.cols - dstWidth;
|
|
|
|
return cv::Mat(image, cv::Rect(x, 0, dstWidth, image.rows));
|
|
}
|
|
|
|
cv::Mat extendHeight(const cv::Mat& image, const int dstHeight)
|
|
{
|
|
cv::Mat result = cv::Mat(dstHeight, image.cols, image.type());
|
|
int totalRowsToAdd = dstHeight - image.rows;
|
|
cv::Mat firstRow = image.row(0);
|
|
cv::Mat lastRow = image.row(image.rows - 1);
|
|
|
|
// duplicate the first row (totalRowsToAdd / 2)-times
|
|
for (int i = 0; i < totalRowsToAdd / 2; i++)
|
|
firstRow.copyTo(result.row(i));
|
|
|
|
// copy the original image under the duplicated first rows
|
|
image.copyTo(result(cv::Rect(0, totalRowsToAdd / 2, image.cols, image.rows)));
|
|
|
|
// duplicate the last row (totalRowsToAdd / 2)-times
|
|
for (int i = totalRowsToAdd / 2 + image.rows; i < result.rows; i++)
|
|
lastRow.copyTo(result.row(i));
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
+--------+----+----+----+----+------+------+------+------+
|
|
| | C1 | C2 | C3 | C4 | C(5) | C(6) | C(7) | C(8) |
|
|
+--------+----+----+----+----+------+------+------+------+
|
|
| CV_8U | 0 | 8 | 16 | 24 | 32 | 40 | 48 | 56 |
|
|
| CV_8S | 1 | 9 | 17 | 25 | 33 | 41 | 49 | 57 |
|
|
| CV_16U | 2 | 10 | 18 | 26 | 34 | 42 | 50 | 58 |
|
|
| CV_16S | 3 | 11 | 19 | 27 | 35 | 43 | 51 | 59 |
|
|
| CV_32S | 4 | 12 | 20 | 28 | 36 | 44 | 52 | 60 |
|
|
| CV_32F | 5 | 13 | 21 | 29 | 37 | 45 | 53 | 61 |
|
|
| CV_64F | 6 | 14 | 22 | 30 | 38 | 46 | 54 | 62 |
|
|
+--------+----+----+----+----+------+------+------+------+
|
|
|
|
*/ |