1.回家过年,总是有意或无意间翻到一些旧时的照片,旧照片作为时光记忆的载体和岁月流转的见证,不单单是过去美好时光的传承者,同时也是每个人的情结和怀念的寄托。随着时间的流逝,许多老照片都因为自然或人为原因,受到了侵蚀损坏,画面模糊、褪色、照片磨损严重等现象,甚至还有的因为保管不好导致照片面目全非。作为一个程序员,随着旧照片的损坏,你再也无法证明你也曾经满头秀发,你也曾经意气风发,有过明亮的眼眸。
2.GFPGAN是Tencent开源的一个旧照片修复算法,它能够让这些老照片恢复原有的光泽,使用了GAN算法对照片进行修复,效果比其他同类模型都有更好的表现。如果对算法和如何训练模型可转到github:https://github.com/TencentARC/GFPGAN .
3.这里只是演示如何使用C++调用官方给出训练好的模型,开发环境是win10, vs2019, opencv4.5, ncnn,如果要启用GPU加速,所以用到VulkanSDK,实现语言是C++。
4.官方给出的测试效果对比图:
C++代码实现:
第一张是原图,第二张是只修复人脸,第三张是修复全身:
#include <vector> #include <ostream> #include <random> #include <chrono> #include <stdio.h> #include <fstream> #include <opencv2/opencv.hpp> #include <ncnn/net.h> #include <ncnn/cpu.h> #include "gfpgan.h" #include "face.h" #include "realesrgan.h" static void toOcv( const ncnn::Mat& result, cv::Mat& out) { cv::Mat cv_result_32F = cv::Mat::zeros(cv::Size(512, 512), CV_32FC3); for (int i = 0; i < result.h; i++) { for (int j = 0; j < result.w; j++) { cv_result_32F.at<cv::Vec3f>(i, j)[2] = (result.channel(0)[i * result.w + j] + 1) / 2; cv_result_32F.at<cv::Vec3f>(i, j)[1] = (result.channel(1)[i * result.w + j] + 1) / 2; cv_result_32F.at<cv::Vec3f>(i, j)[0] = (result.channel(2)[i * result.w + j] + 1) / 2; } } cv::Mat cv_result_8U; cv_result_32F.convertTo(cv_result_8U, CV_8UC3, 255.0, 0); cv_result_8U.copyTo(out); } void mergeImage(std::vector<cv::Mat>& src_vor, cv::Mat& cv_dst, int channel) { cv::Mat img_merge; cv::Size size(src_vor.at(0).cols * src_vor.size(), src_vor.at(0).rows); if (channel == 1) { img_merge.create(size, CV_8UC1); } else if (channel == 3) { img_merge.create(size, CV_8UC3); } for (int i = 0; i < src_vor.size(); i++) { cv::Mat cv_temp = img_merge(cv::Rect(src_vor.at(i).cols * i, 0, src_vor.at(i).cols, src_vor.at(i).rows)); src_vor.at(i).copyTo(cv_temp); } cv_dst = img_merge.clone(); } static void pasteFacesInputImage(const cv::Mat& restored_face,cv::Mat& trans_matrix_inv,cv::Mat& bg_upsample) { trans_matrix_inv.at<float>(0, 2) += 1.0; trans_matrix_inv.at<float>(1, 2) += 1.0; cv::Mat inv_restored; cv::warpAffine(restored_face, inv_restored, trans_matrix_inv, bg_upsample.size(), 1, 0); cv::Mat mask = cv::Mat::ones(cv::Size(512, 512), CV_8UC1) * 255; cv::Mat inv_mask; cv::warpAffine(mask, inv_mask, trans_matrix_inv, bg_upsample.size(), 1, 0); cv::Mat inv_mask_erosion; cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(4, 4)); cv::erode(inv_mask, inv_mask_erosion, kernel); cv::Mat pasted_face; cv::bitwise_and(inv_restored, inv_restored, pasted_face, inv_mask_erosion); int total_face_area = cv::countNonZero(inv_mask_erosion); int w_edge = int(std::sqrt(total_face_area) / 20); int erosion_radius = w_edge * 2; cv::Mat inv_mask_center; kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(erosion_radius, erosion_radius)); cv::erode(inv_mask_erosion, inv_mask_center, kernel); int blur_size = w_edge * 2; cv::Mat inv_soft_mask; cv::GaussianBlur(inv_mask_center, inv_soft_mask, cv::Size(blur_size + 1, blur_size + 1), 0, 0, 4); for (int h = 0; h < bg_upsample.rows; h++) { for (int w = 0; w < bg_upsample.cols; w++) { float alpha = inv_soft_mask.at<uchar>(h, w) / 255.0; bg_upsample.at<cv::Vec3b>(h, w)[0] = pasted_face.at<cv::Vec3b>(h, w)[0] * alpha + (1 - alpha) * bg_upsample.at<cv::Vec3b>(h, w)[0]; bg_upsample.at<cv::Vec3b>(h, w)[1] = pasted_face.at<cv::Vec3b>(h, w)[1] * alpha + (1 - alpha) * bg_upsample.at<cv::Vec3b>(h, w)[1]; bg_upsample.at<cv::Vec3b>(h, w)[2] = pasted_face.at<cv::Vec3b>(h, w)[2] * alpha + (1 - alpha) * bg_upsample.at<cv::Vec3b>(h, w)[2]; } } } int main(int argc, char** argv) { GFPGAN gfpgan; gfpgan.initModel("models/encoder.param", "models/encoder.bin", "models/style.bin"); Face face_detector; face_detector.initModel("models/blazeface.param", "models/blazeface.bin"); RealESRGAN real_esrgan; real_esrgan.initModel("models/real_esrgan.param", "models/real_esrgan.bin"); std::string path = "images"; std::vector<std::string> filenames; cv::glob(path, filenames, false); int i = 0; for (auto v : filenames) { cv::Mat img = cv::imread(v, 1); cv::Mat bg_upsample; real_esrgan.tile_process(img, bg_upsample); std::vector<cv::Mat> trans_img; std::vector<cv::Mat> trans_matrix_inv; std::vector<Object> objects; face_detector.detect(img, objects); face_detector.align_warp_face(img, objects, trans_matrix_inv, trans_img); for (size_t i = 0; i < objects.size(); i++) { ncnn::Mat gfpgan_result; gfpgan.process(trans_img[i], gfpgan_result); cv::Mat restored_face; toOcv(gfpgan_result, restored_face); pasteFacesInputImage(restored_face, trans_matrix_inv[i], bg_upsample); } ncnn::Mat gfpgan_result; gfpgan.process(img, gfpgan_result); cv::Mat restored_face; toOcv(gfpgan_result, restored_face); cv::resize(img, img, bg_upsample.size()); cv::resize(restored_face, restored_face, bg_upsample.size()); std::vector<cv::Mat> cv_dsts{ img, restored_face, bg_upsample }; cv::Mat cv_dst; mergeImage(cv_dsts, cv_dst, 3); cv::imwrite(std::to_string(i)+".png", cv_dst); i++; } return 0; }
效果图: