实现原理
图像着色最早是应用在图像修复方面,将一些过去的黑白旧照根据预设色盘上色,得到色彩饱满的彩色图,比如0灰度对应某个RGB数值,120灰度对应某个RGB数值等等,这也是当前OpenCV中已集成好的applycolormap(伪彩色函数)实现原理,按照不同的色盘给灰度图上色,可得到不同样式的伪彩色图,像当前深度图像、红外成像、雷达地图成像等领域就采用这类方法实现图像色彩重绘。
若要将图像上色为符合现实逻辑的语义颜色和色调,就不能单单依靠固定的色盘方法,过去常采用的方案一般是依赖人主观的上色能力,就如PS中,可以通过控制色彩曲线、颜色占比等方法将黑白图慢慢恢复成彩色图。而随着深度学习、计算机视觉近几年的快速发展,将灰度图智能且高效地上色成为可能。基于图像着色算法和caffe、tensorflow、pytorch等深度学习框架,将相关的巨量数据集训练成具备一定预测能力的深度学习模型,通过这些模型即可实现更优的图像着色效果。
本文通过OpenCV中DNN模块导入深度学习模型的方法,来实现图像着色效果。
具体流程
1)加载模型信息,模型下载链接在下方,若不想用钱下载可以三连,评论留下邮箱我会尽快发送完整模型文件,确保打开即用。
string modelTxt = "colorization_deploy_v2.prototxt"; string modelBin = "colorization_release_v2.caffemodel"; Net net = dnn::readNetFromCaffe(modelTxt, modelBin);
2)设置相关参数。
const int W_in = 224; const int H_in = 224; int sz[] = { 2, 313, 1, 1 }; const Mat Pts_in_hull(4, sz, CV_32F, pts_in_hull); Ptr<dnn::Layer> class8_ab = net.getLayer("class8_ab"); class8_ab->blobs.emplace_back(Pts_in_hull); Ptr<dnn::Layer> conv8_313_rh = net.getLayer("conv8_313_rh"); conv8_313_rh->blobs.emplace_back(Mat(1, 313, CV_32F, Scalar(2.606)));
3)将图像转化为Lab颜色空间,提取L通道操作,这样的好处是仅操作亮度即可,如果用RGB,那要同时处理三个通道的数据,而三个参数调控难度太大。
Mat lab, L, input; img.convertTo(img, CV_32F, 1.0 / 255); cvtColor(img, lab, COLOR_BGR2Lab); extractChannel(lab, L, 0); resize(L, input, Size(W_in, H_in)); input -= 50;
4)将L通道图像输入到网络中,前向计算,从网络输出中提取a和b通道,组合成彩色图即完成。
Size siz(result.size[2], result.size[3]); Mat a = Mat(siz, CV_32F, result.ptr(0, 0)); Mat b = Mat(siz, CV_32F, result.ptr(0, 1)); resize(a, a, img.size()); resize(b, b, img.size()); Mat color, chn[] = { L, a, b }; merge(chn, 3, lab); cvtColor(lab, color, COLOR_Lab2BGR);
C++测试代码
#include <opencv2/dnn.hpp> #include <opencv2/imgproc.hpp> #include <opencv2/highgui.hpp> #include <iostream> using namespace cv; using namespace cv::dnn; using namespace std; // 通过pts_in_hull.npy转化 static float pts_in_hull[] = { -90., -90., -90., -90., -90., -80., -80., -80., -80., -80., -80., -80., -80., -70., -70., -70., -70., -70., -70., -70., -70., -70., -70., -60., -60., -60., -60., -60., -60., -60., -60., -60., -60., -60., -60., -50., -50., -50., -50., -50., -50., -50., -50., -50., -50., -50., -50., -50., -50., -40., -40., -40., -40., -40., -40., -40., -40., -40., -40., -40., -40., -40., -40., -40., -30., -30., -30., -30., -30., -30., -30., -30., -30., -30., -30., -30., -30., -30., -30., -30., -20., -20., -20., -20., -20., -20., -20., -20., -20., -20., -20., -20., -20., -20., -20., -20., -10., -10., -10., -10., -10., -10., -10., -10., -10., -10., -10., -10., -10., -10., -10., -10., -10., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 30., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 40., 50., 50., 50., 50., 50., 50., 50., 50., 50., 50., 50., 50., 50., 50., 50., 50., 50., 50., 50., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 60., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 70., 80., 80., 80., 80., 80., 80., 80., 80., 80., 80., 80., 80., 80., 80., 80., 80., 80., 80., 80., 90., 90., 90., 90., 90., 90., 90., 90., 90., 90., 90., 90., 90., 90., 90., 90., 90., 90., 90., 100., 100., 100., 100., 100., 100., 100., 100., 100., 100., 50., 60., 70., 80., 90., 20., 30., 40., 50., 60., 70., 80., 90., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., -20., -10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., -30., -20., -10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 100., -40., -30., -20., -10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 100., -50., -40., -30., -20., -10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 100., -50., -40., -30., -20., -10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 100., -60., -50., -40., -30., -20., -10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 100., -70., -60., -50., -40., -30., -20., -10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 100., -80., -70., -60., -50., -40., -30., -20., -10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., -80., -70., -60., -50., -40., -30., -20., -10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., -90., -80., -70., -60., -50., -40., -30., -20., -10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., -100., -90., -80., -70., -60., -50., -40., -30., -20., -10., 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., -100., -90., -80., -70., -60., -50., -40., -30., -20., -10., 0., 10., 20., 30., 40., 50., 60., 70., 80., -110., -100., -90., -80., -70., -60., -50., -40., -30., -20., -10., 0., 10., 20., 30., 40., 50., 60., 70., 80., -110., -100., -90., -80., -70., -60., -50., -40., -30., -20., -10., 0., 10., 20., 30., 40., 50., 60., 70., 80., -110., -100., -90., -80., -70., -60., -50., -40., -30., -20., -10., 0., 10., 20., 30., 40., 50., 60., 70., -110., -100., -90., -80., -70., -60., -50., -40., -30., -20., -10., 0., 10., 20., 30., 40., 50., 60., 70., -90., -80., -70., -60., -50., -40., -30., -20., -10., 0. }; int main() { string modelTxt = "colorization_deploy_v2.prototxt"; string modelBin = "colorization_release_v2.caffemodel"; string imageFile = "test.jpg"; string original = "zhu.jpg"; // 读取灰度图用来作颜色还原 Mat gray = imread(original, 0); // 原图对比 Mat ori = imread(original); imwrite(imageFile, gray); Mat img = imread(imageFile); if (img.empty()) { cout << "Can't read image from file: " << imageFile << endl; return 2; } // 预训练网络的固定输入大小 const int W_in = 224; const int H_in = 224; Net net = dnn::readNetFromCaffe(modelTxt, modelBin); // 设置训练得到的参数数据 int sz[] = { 2, 313, 1, 1 }; const Mat Pts_in_hull(4, sz, CV_32F, pts_in_hull); Ptr<dnn::Layer> class8_ab = net.getLayer("class8_ab"); class8_ab->blobs.emplace_back(Pts_in_hull); Ptr<dnn::Layer> conv8_313_rh = net.getLayer("conv8_313_rh"); conv8_313_rh->blobs.emplace_back(Mat(1, 313, CV_32F, Scalar(2.606))); // 提取L通道灰度图,并均值化 Mat lab, L, input; img.convertTo(img, CV_32F, 1.0 / 255); cvtColor(img, lab, COLOR_BGR2Lab); extractChannel(lab, L, 0); resize(L, input, Size(W_in, H_in)); input -= 50; // L通道图像输入到网络,前向计算 Mat inputBlob = blobFromImage(input); net.setInput(inputBlob); Mat result = net.forward(); // 从网络输出中提取得到的a,b通道 Size siz(result.size[2], result.size[3]); Mat a = Mat(siz, CV_32F, result.ptr(0, 0)); Mat b = Mat(siz, CV_32F, result.ptr(0, 1)); resize(a, a, img.size()); resize(b, b, img.size()); // 通道合并转换成彩色图 Mat color, chn[] = { L, a, b }; merge(chn, 3, lab); cvtColor(lab, color, COLOR_Lab2BGR); // 结果展示 color.convertTo(color, CV_8U, 255.); imshow("color", color); imshow("gray", gray); imshow("ori", ori); waitKey(); return 0; }
测试效果
图1 原图
图2 灰度图
图3 着色图
不难看出,还原的着色图还是比较符合现实语义色调的,不过还是同原图的一些色彩有所差异,毕竟数据量有限。这个数据集当初估计没少放黄色调的图,处理了好多图像都偏暗黄系。
注意:测试中发现,OpenCV版本为4以上,debug和release都没问题;3.4版本的debug没问题,release总是报错。所以建议用OpenCV4。