OpenCV 的 Contrast Preserving Decolorization 源码解析

举报
ShaderJoy 发表于 2021/12/31 23:56:22 2021/12/31
【摘要】 运行效果为:  出乎我意料的是,不仅仅保留了对比度,居然还增强了图像的对比度(去雾,不过只适用于比较均匀的雾),不过运行的速度堪忧,500*500的图像都需要 1s 多! 经过 OpenMP 优化,执行时间减少了一半左右   该代码是源于 香港中文大学 计算机科学与工...

运行效果为:

 出乎我意料的是,不仅仅保留了对比度,居然还增强了图像的对比度(去雾,不过只适用于比较均匀的雾),不过运行的速度堪忧,500*500的图像都需要 1s 多!

经过 OpenMP 优化,执行时间减少了一半左右

 

该代码是源于 香港中文大学 计算机科学与工程系 的一篇论文 Contrast Preserving Decolorization

其代码已被收录到 OpenCV 的源码中,位于(注意,3.* 以上才有)

 

以下是在通读了论文《IEEE International Conference on Computational Photography(ICCP), 2012》之后,对原始代码做的详细注释( OpenCV 原代码中有多处 Bug,我已 fix 掉):


  
  1. void deColor(InputArray _src, OutputArray _dst, OutputArray _color_boost)
  2. {
  3. Mat I = _src.getMat();
  4. _dst.create(I.size(), CV_8UC1);
  5. Mat dst = _dst.getMat();
  6. _color_boost.create(I.size(), CV_8UC3);
  7. Mat color_boost = _color_boost.getMat();
  8. CV_Assert(!I.empty() && (I.channels() == 3));
  9. // Parameter Setting
  10. const int maxIter = 15;
  11. const double tol = .0001;
  12. int iterCount = 0;
  13. double E = 0;
  14. double pre_E = std::numeric_limits<double>::infinity(); // 返回编译器允许的 double 型的正无穷大
  15. Mat img;
  16. I.convertTo(img, CV_32FC3, 1.0 / 255.0); // 8UC3 -> 32FC3,归一化
  17. // Initialization
  18. Decolor obj;
  19. vector <double> Cg;
  20. vector < vector <double> > polyGrad;
  21. vector <Vec3i> comb;
  22. vector <double> alf;
  23. obj.gradSystem(img, polyGrad, Cg, comb);
  24. obj.weakOrder(img, alf);
  25. // Solver
  26. Mat Mt = Mat(int(polyGrad.size()), int(polyGrad[0].size()), CV_32FC1);
  27. obj.weightUpdateMatrix(polyGrad, Cg, Mt);
  28. vector <double> wei;
  29. obj.weightInit(comb, wei);
  30. main loop starting
  31. vector <double> G_pos(alf.size());
  32. vector <double> G_neg(alf.size());
  33. vector <double> EXPsum(G_pos.size());
  34. vector <double> EXPterm(G_pos.size());
  35. vector <double> temp(polyGrad[0].size());
  36. vector <double> temp1(polyGrad[0].size());
  37. vector <double> temp2(EXPsum.size());
  38. vector <double> wei1(polyGrad.size());
  39. while (sqrt(pow(E - pre_E, 2)) > tol)
  40. {
  41. iterCount += 1;
  42. pre_E = E;
  43. // 梯度图大小
  44. for (size_t i = 0; i < polyGrad[0].size(); i++)
  45. {
  46. double val = 0.0;
  47. // 基的个数,公式(10)
  48. for (size_t j = 0; j < polyGrad.size(); j++)
  49. val = val + (polyGrad[j][i] * wei[j]);
  50. // 公式(4) 的绝对值内部
  51. temp[i] = val - Cg[i];
  52. temp1[i] = val + Cg[i];
  53. }
  54. // 近似公式(4)
  55. for (size_t i = 0; i < alf.size(); i++)
  56. {
  57. const double sqSigma = obj.sigma * obj.sigma;
  58. const double pos = ((1 + alf[i]) / 2) * exp(-1.0 * 0.5 * (temp[i] * temp[i]) / sqSigma);
  59. const double neg = ((1 - alf[i]) / 2) * exp(-1.0 * 0.5 * (temp1[i] * temp1[i]) / sqSigma);
  60. G_pos[i] = pos;
  61. G_neg[i] = neg;
  62. }
  63. // 近似公式(12)(14)
  64. for (size_t i = 0; i < G_pos.size(); i++)
  65. EXPsum[i] = G_pos[i] + G_neg[i];
  66. for (size_t i = 0; i < EXPsum.size(); i++)
  67. temp2[i] = (EXPsum[i] == 0) ? 1.0 : 0.0;
  68. for (size_t i = 0; i < G_pos.size(); i++)
  69. EXPterm[i] = (G_pos[i] - G_neg[i]) / (EXPsum[i] + temp2[i]);
  70. // 更新权重
  71. for (int i = 0; i < int(polyGrad.size()); i++)
  72. {
  73. double val1 = 0.0;
  74. for (int j = 0; j < int(polyGrad[0].size()); j++)
  75. {
  76. val1 = val1 + (Mt.at<float>(i, j) * EXPterm[j]);
  77. }
  78. wei1[i] = val1;
  79. }
  80. // 替换权重
  81. for (size_t i = 0; i < wei.size(); i++)
  82. wei[i] = wei1[i];
  83. // 公式(11)
  84. E = obj.energyCalcu(Cg, polyGrad, wei);
  85. if (iterCount > maxIter)
  86. break;
  87. }
  88. Mat Gray = Mat::zeros(img.size(), CV_32FC1);
  89. obj.grayImContruct(wei, img, Gray);
  90. Gray.convertTo(dst, CV_8UC1, 255);
  91. /// Contrast Boosting /
  92. Mat lab;
  93. cvtColor(I, lab, COLOR_BGR2Lab);
  94. vector <Mat> lab_channel;
  95. split(lab, lab_channel);
  96. dst.copyTo(lab_channel[0]); // 仅仅替换 L 通道
  97. merge(lab_channel, lab);
  98. cvtColor(lab, color_boost, COLOR_Lab2BGR);
  99. }
  100. double Decolor::energyCalcu(const vector <double> &Cg, const vector < vector <double> > &polyGrad, const vector <double> &wei) const
  101. {
  102. const size_t size = polyGrad[0].size();
  103. vector <double> energy(size);
  104. vector <double> temp(size);
  105. vector <double> temp1(size);
  106. // 公式(11)两个 exp {} 中的部分
  107. // 公式中的 l(x,y)
  108. for (size_t i = 0; i < polyGrad[0].size(); i++)
  109. {
  110. double val = 0.0;
  111. // 公式中的 i
  112. for (size_t j = 0; j < polyGrad.size(); j++)
  113. val = val + (polyGrad[j][i] * wei[j]);
  114. temp[i] = val - Cg[i];
  115. temp1[i] = val + Cg[i];
  116. }
  117. // 注意这里和公式(11)不同,左右两边的比重都为 1
  118. for (size_t i = 0; i < polyGrad[0].size(); i++)
  119. energy[i] = -1.0 * log(exp(-1.0 * pow(temp[i], 2) / sigma) + exp(-1.0 * pow(temp1[i], 2) / sigma));
  120. double sum = 0.0;
  121. // 把整幅图的能量(代价)都相加起来
  122. for (size_t i = 0; i < polyGrad[0].size(); i++)
  123. sum += energy[i];
  124. // 平均
  125. return (sum / polyGrad[0].size());
  126. }
  127. Decolor::Decolor()
  128. {
  129. kernelx = Mat(1, 2, CV_32FC1);
  130. kernely = Mat(2, 1, CV_32FC1);
  131. kernelx.at<float>(0, 0) = 1.0; // 1., -1.
  132. kernelx.at<float>(0, 1) = -1.0;
  133. kernely.at<float>(0, 0) = 1.0; // 1.; -1.
  134. kernely.at<float>(1, 0) = -1.0;
  135. order = 2; // degree
  136. sigma = 0.02f;
  137. }
  138. vector<double> Decolor::product(const vector <Vec3i> &comb, const double initRGB[3])
  139. {
  140. vector <double> res(comb.size());
  141. // 从 comb 容器中,逐个取出 vec3 和 rgb 进行点乘
  142. for (size_t i = 0; i < comb.size(); i++)
  143. {
  144. double dp = 0.0;
  145. for (int j = 0; j < 3; j++)
  146. dp = dp + (comb[i][j] * initRGB[j]);
  147. res[i] = dp;
  148. }
  149. // 返回每个 vec3 点乘的结果
  150. return res;
  151. }
  152. // 计算横向的单通道梯度图
  153. void Decolor::singleChannelGradx(const Mat &img, Mat &dest) const
  154. {
  155. const int w = img.size().width;
  156. // kernels.cols/2-1, kernelx.rows/2-1
  157. // 调整卷积图像的锚点,默认是 (-1,-1)
  158. // Σ kernel(x',y')*src(x+x'-anchor.x, y+y'-anchor.y)
  159. const Point anchor(kernelx.cols - kernelx.cols / 2 - 1, kernelx.rows - kernelx.rows / 2 - 1);
  160. filter2D(img, dest, -1, kernelx, anchor, 0.0, BORDER_CONSTANT); // 超出边界的地方用常数填充
  161. dest.col(w - 1) = 0.0;// 最右侧设置为 0
  162. }
  163. // 计算纵向的单通道梯度图
  164. void Decolor::singleChannelGrady(const Mat &img, Mat &dest) const
  165. {
  166. const int h = img.size().height;
  167. const Point anchor(kernely.cols - kernely.cols / 2 - 1, kernely.rows - kernely.rows / 2 - 1);
  168. filter2D(img, dest, -1, kernely, anchor, 0.0, BORDER_CONSTANT);
  169. dest.row(h - 1) = 0.0;
  170. }
  171. // 计算横向和纵向的梯度(转置)并合并到一个容器中
  172. void Decolor::gradvector(const Mat &img, vector <double> &grad) const
  173. {
  174. Mat dest;
  175. Mat dest1;
  176. singleChannelGradx(img, dest);
  177. singleChannelGrady(img, dest1);
  178. // 得到转置的单通道梯度图
  179. Mat d_trans = dest.t();
  180. Mat d1_trans = dest1.t();
  181. const int height = d_trans.size().height;
  182. const int width = d_trans.size().width;
  183. // 把两张梯度图合并到一个容器当中(前:横向,后:纵向)
  184. // OpenCV 源码中此处有越界问题(难以置信)
  185. grad.resize(width * height * 2);
  186. for (int i = 0; i < height; i++)
  187. for (int j = 0; j < width; j++)
  188. grad[i * width + j] = d_trans.at<float>(i, j);
  189. const int offset = width * height;
  190. for (int i = 0; i < height; i++)
  191. for (int j = 0; j < width; j++)
  192. grad[offset + i * width + j] = d1_trans.at<float>(i, j);
  193. }
  194. // 计算 CIELab 空间的颜色对比度
  195. void Decolor::colorGrad(const Mat &img, vector <double> &Cg) const
  196. {
  197. Mat lab;
  198. // 转换到 CIELab 颜色空间
  199. cvtColor(img, lab, COLOR_BGR2Lab);
  200. vector <Mat> lab_channel;
  201. split(lab, lab_channel);
  202. vector <double> ImL;
  203. vector <double> Ima;
  204. vector <double> Imb;
  205. // 分别计算 Lab 三个通道的梯度图
  206. gradvector(lab_channel[0], ImL);
  207. gradvector(lab_channel[1], Ima);
  208. gradvector(lab_channel[2], Imb);
  209. Cg.resize(ImL.size());
  210. // 计算颜色对比度(Color Contrast)
  211. for (size_t i = 0; i < ImL.size(); i++)
  212. {
  213. const double res = sqrt(pow(ImL[i], 2) + pow(Ima[i], 2) + pow(Imb[i], 2)) / 100;
  214. Cg[i] = res;
  215. }
  216. }
  217. // 构造一个 vec3 张量,并添加到 comb 容器中
  218. void Decolor::addVector(vector <Vec3i> &comb, int &idx, int r, int g, int b)
  219. {
  220. comb.push_back(Vec3i(r, g, b));
  221. idx++;
  222. }
  223. // 保存梯度容器到一个容器的容器
  224. void Decolor::addToVectorPoly(vector < vector <double> > &polyGrad, const vector <double> &curGrad, int &idx1)
  225. {
  226. polyGrad.push_back(curGrad);
  227. idx1++;
  228. }
  229. // 和论文上公式不同
  230. void Decolor::weakOrder(const Mat &src, vector <double> &alf) const
  231. {
  232. const int h = src.size().height;
  233. const int w = src.size().width;
  234. cv::Mat img;
  235. if ((h + w) > 800)
  236. {
  237. const double sizefactor = double(800) / (h + w);
  238. resize(src, img, Size(cvRound(w * sizefactor), cvRound(h * sizefactor)));
  239. }
  240. else
  241. img = src;
  242. Mat curIm = Mat(img.size(), CV_32FC1);
  243. vector <Mat> rgb_channel;
  244. split(img, rgb_channel);// 又对图像进行了分裂
  245. vector <double> Rg, Gg, Bg;
  246. gradvector(rgb_channel[2], Rg); // 计算 RGB 三个通道的梯度图
  247. gradvector(rgb_channel[1], Gg);
  248. gradvector(rgb_channel[0], Bg);
  249. vector <double> t1(Rg.size()), t2(Rg.size()), t3(Rg.size());
  250. vector <double> tmp1(Rg.size()), tmp2(Rg.size()), tmp3(Rg.size());
  251. const double level = .05;
  252. // 和 level 、-level 进行比较
  253. for (size_t i = 0; i < Rg.size(); i++)
  254. {
  255. t1[i] = (Rg[i] > level) ? 1.0 : 0.0;
  256. t2[i] = (Gg[i] > level) ? 1.0 : 0.0;
  257. t3[i] = (Bg[i] > level) ? 1.0 : 0.0;
  258. tmp1[i] = (Rg[i] < -1.0 * level) ? 1.0 : 0.0;
  259. tmp2[i] = (Gg[i] < -1.0 * level) ? 1.0 : 0.0;
  260. tmp3[i] = (Bg[i] < -1.0 * level) ? 1.0 : 0.0;
  261. }
  262. alf.resize(Rg.size());
  263. for (size_t i = 0; i < Rg.size(); i++)
  264. alf[i] = (t1[i] * t2[i] * t3[i]);
  265. for (size_t i = 0; i < Rg.size(); i++)
  266. alf[i] -= tmp1[i] * tmp2[i] * tmp3[i];
  267. }
  268. // 构造 polynomial space 每个基的梯度图,还有得到 CIELab 颜色空间的对比度,以及 polynomial space 的基
  269. void Decolor::gradSystem(const Mat &src, vector < vector < double > > &polyGrad,
  270. vector < double > &Cg, vector <Vec3i> &comb) const
  271. {
  272. int h = src.size().height;
  273. int w = src.size().width;
  274. cv::Mat img;
  275. // 如果宽高和超过一定大小则进行缩放
  276. if ((h + w) > 800)
  277. {
  278. const double sizefactor = double(800) / (h + w);
  279. resize(src, img, Size(cvRound(w * sizefactor), cvRound(h * sizefactor)));
  280. }
  281. else
  282. img = src;
  283. h = img.size().height;
  284. w = img.size().width;
  285. colorGrad(img, Cg);
  286. // 将一副图像映射到 polynomial space
  287. Mat curIm = Mat(img.size(), CV_32FC1);
  288. vector <Mat> rgb_channel;
  289. split(img, rgb_channel); // 得到 BGR 三个通道
  290. int idx = 0, idx1 = 0;
  291. for (int r = 0; r <= order; r++)
  292. for (int g = 0; g <= order; g++)
  293. for (int b = 0; b <= order; b++)
  294. {
  295. if ((r + g + b) <= order && (r + g + b) > 0)
  296. {
  297. // 保存 polynomial space 的基
  298. addVector(comb, idx, r, g, b);
  299. // 每个 polynomial space 的基(r, g, b) 都要对整张图像进行计算(w*h 大小)
  300. for (int i = 0; i < h; i++)
  301. for (int j = 0; j < w; j++)
  302. // 映射每一个 rgb 像素
  303. curIm.at<float>(i, j) =
  304. pow(rgb_channel[2].at<float>(i, j), r) *
  305. pow(rgb_channel[1].at<float>(i, j), g) *
  306. pow(rgb_channel[0].at<float>(i, j), b);
  307. vector <double> curGrad;
  308. gradvector(curIm, curGrad);
  309. // 保存每个基计算的梯度图
  310. addToVectorPoly(polyGrad, curGrad, idx1);
  311. }
  312. }
  313. }
  314. void Decolor::weightUpdateMatrix(const vector < vector <double> > &poly, const vector <double> &Cg, Mat &X)
  315. {
  316. // 容器转为矩阵
  317. const int size = static_cast<int>(poly.size());
  318. const int size0 = static_cast<int>(poly[0].size());
  319. Mat P = Mat(size, size0, CV_32FC1);
  320. for (int i = 0; i < size; i++)
  321. for (int j = 0; j < size0; j++)
  322. P.at<float>(i, j) = static_cast<float>(poly[i][j]);
  323. // 转置
  324. const Mat P_trans = P.t();
  325. Mat B = Mat(size, size0, CV_32FC1);
  326. for (int i = 0; i < size; i++)
  327. {
  328. for (int j = 0, end = int(Cg.size()); j < end; j++)
  329. B.at<float>(i, j) = static_cast<float>(poly[i][j] * Cg[j]);
  330. }
  331. // 得到一个方阵,大小为 size*size
  332. Mat A = P * P_trans;
  333. // 求解线性方程组,这里的 X 应该是论文公式(14)的部分
  334. // cv::solve -- https://docs.opencv.org/3.1.0/d2/de8/group__core__array.html#ga12b43690dbd31fed96f213eefead2373
  335. // DECOMP_NORMAL -- https://docs.opencv.org/3.1.0/d2/de8/group__core__array.html#gaaf9ea5dcc392d5ae04eacb9920b9674c
  336. // 这意味着求解的方程是 src1 T⋅src1⋅dst = src1 T src2,而不是原始方程 src1⋅dst = src2
  337. solve(A, B, X, DECOMP_NORMAL);
  338. //---------------------
  339. // DECOMP_LU(LU分解)
  340. // http://blog.csdn.net/myhaspl/article/details/49450743
  341. // DECOMP_CHOLESKY(CHOLESKY分解)
  342. // http://blog.csdn.net/acdreamers/article/details/44656847
  343. // DECOMP_EIG(EIG分解)
  344. // DECOMP_SVD(SVD分解)
  345. // http://www.cnblogs.com/LeftNotEasy/archive/2011/01/19/svd-and-applications.html
  346. // DECOMP_QR(QR分解)
  347. // http://blog.sina.com.cn/s/blog_3f41287a0101ke2s.html
  348. //---------------------
  349. }
  350. void Decolor::weightInit(const vector <Vec3i> &comb, vector <double> &wei)
  351. {
  352. double initRGB[3] = { .33, .33, .33 };
  353. // 通过 polynomial space 的基和 rgb 系数进行点乘,对 weight 进行初始化
  354. wei = product(comb, initRGB);
  355. vector <int> sum(comb.size());
  356. for (size_t i = 0; i < comb.size(); i++)
  357. sum[i] = (comb[i][0] + comb[i][1] + comb[i][2]);
  358. // 除了 r,g,b 这三种基,其他基的权重初始化为 0
  359. for (size_t i = 0; i < sum.size(); i++)
  360. {
  361. if (sum[i] == 1)
  362. wei[i] = wei[i] * double(1);
  363. else
  364. wei[i] = wei[i] * double(0);
  365. }
  366. sum.clear();
  367. }
  368. // 将迭代出各个基的权重和各个基相乘,并把结果累加到原灰度上
  369. void Decolor::grayImContruct(vector <double> &wei, const Mat &img, Mat &Gray) const
  370. {
  371. const int h = img.size().height;
  372. const int w = img.size().width;
  373. vector <Mat> rgb_channel;
  374. split(img, rgb_channel);
  375. int kk = 0;
  376. for (int r = 0; r <= order; r++)
  377. for (int g = 0; g <= order; g++)
  378. for (int b = 0; b <= order; b++)
  379. if ((r + g + b) <= order && (r + g + b) > 0)
  380. {
  381. for (int i = 0; i < h; i++)
  382. for (int j = 0; j < w; j++)
  383. Gray.at<float>(i, j) = Gray.at<float>(i, j) +
  384. static_cast<float>(wei[kk]) * pow(rgb_channel[2].at<float>(i, j), r) * pow(rgb_channel[1].at<float>(i, j), g) *
  385. pow(rgb_channel[0].at<float>(i, j), b);
  386. kk = kk + 1; // 遍历各个基
  387. }
  388. // 找出最值,并调整归一化便于显示
  389. double minval, maxval;
  390. minMaxLoc(Gray, &minval, &maxval);
  391. Gray -= minval;
  392. Gray /= maxval - minval;
  393. }

 

文章来源: panda1234lee.blog.csdn.net,作者:panda1234lee,版权归原作者所有,如需转载,请联系作者。

原文链接:panda1234lee.blog.csdn.net/article/details/87978829

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。