用OpenCV实现Photoshop算法(三): 曲线调整

摘要:
关于曲线技术原理的在线资料不完整。

http://blog.csdn.net/c80486/article/details/52499919

系列文章:

用OpenCV实现Photoshop算法(一): 图像旋转

用OpenCV实现Photoshop算法(二): 图像剪切

用OpenCV实现Photoshop算法(三): 曲线调整

用OpenCV实现Photoshop算法(四): 色阶调整

用OpenCV实现Photoshop算法(五): 亮度对比度调整

用OpenCV实现Photoshop算法(六): 变为黑白图像

用OpenCV实现Photoshop算法(七): 调整色相饱和度

用OpenCV实现Photoshop算法(八): 可选颜色

用OpenCV实现Photoshop算法(九): 高反差保留

三、曲线调整( Curves Adjustment )

曲线调整是Photoshop的最常用的重要功能之一。

网上关于曲线技术原理的材料都不完整。经过一个多月的探索、不断实验,我用OpenCV实现了曲线功能,基本算是揭开了“曲线之谜“。

(一)曲线原理

对于一个RGB图像, 可以对R, G, B 通道进行独立的曲线调整,即,对三个通道分别使用三条曲线(Curve)。还可以再增加一条曲线对 三个通道进行整体调整。因此,对一个图像,可以用四条曲线调整。最终的结果,是四条曲线调整后合并产生的结果。

我们先来分析对单通道一条曲线的原理,比如:对红色通道定义一条曲线如下:

用OpenCV实现Photoshop算法(三): 曲线调整第1张

图中,横轴是输入,比左到右分别表示0到255. 纵轴是输出,从下到上分别表示0到255.

该曲线由三个点定义,座标分别为: 点1(0,0), 点2(127,154),点3(255,255)

点1和点3是默认产生的, 点2是我们新增加的。在这三个点中画出一条曲线(Spline).

调整的实现: 当输入(红色通道值)为X1时,将输出值(新的红色通道值)设为曲线对应的值 Y1.

代码实现: 对图片的所有像素点进行扫描, 取红色值 X1, 换为 对应的 Y1. 其它两个通道值(绿蓝)不变。

比如: 像素点的RGB= (127, 230, 220), 其中红色值为 X1 = 127, 对应曲线上的值Y1 = 154, 则对该通道曲线调整后像素点的RGB= (154, 230, 220)

如果曲线仅是一条由左下角到右上角的45度斜线,则 X1 总是等于 Y1, 则曲线调整后 图片不变。

对红、绿、蓝三个独立通道调整方式都与上述算法相同。各通道调整是互不相关的。

然后,我们再来分析对RGB通道进行整体调整的原理。

比如: 像素点的RGB= (127, 230, 220), 对RGB通道进行整体调整, 则根据该曲线同时对R, G, B三个值进行调整。

R = 127 作为输入值, 计算曲线上的 对应输出值 R1

G = 230作为输入值, 计算曲线上的 对应输出值 G1

B = 220作为输入值,计算曲线上的 对应输出值 B1

则新的像素点的RGB =(R1, G1, B1)

用几条曲线同时调整时,先对红、绿、蓝三个独立通道分别进行调整,最后对RGB总通道进行调整。

由于曲线调整仅仅是数值替换,可以用一个转换表进行快速运算, 因此,曲线调整的速度是很快的。

(二)曲线的生成

Photoshop使用的曲线是一种SPline 曲线。这种曲线表现力很强,特点是:仅需要定义几个控制点,就可以定义一条平滑的曲线,且曲线同时通过所有控制点。生成曲线时,只需要给出几个控制点,调用曲线生成函数即可。

SPline的具体数学原理我就不讲了,生成函数可以看下面的源码Curves.cpp中的spline()函数

(三)曲线调整的opencv实现

我用opencv写了两个 C++ 类:Curves类实现了多通道的曲线的定义、绘制、实施调整。 Curve类是一个通道的曲线定义类。

源码共两个文件: Curves.hpp, Curves.cpp, 源码及使用例程可在这里下载:曲线算法源码

源码有一定的长度,不具体解释了,请见注释。补充说明几点:

1,Curves类中定义了四个Curve对象(即四个通道),分别是RedChannel, GreenChannel, BlueChannel 和 RGBChannel.

2, Curves类支持用鼠标生成曲线,使用方法见例程。

2, Curves.cpp中的spline()函数是生成曲线数值的,即输入一串控制点,通过插值运算,生成一系列的输出值。

3, 除了用鼠标生成曲线以外, 也可以用程序代码直接生成曲线:

先使用Curve类的clearPoints()方法清除所有控制点,再调用addPoint()方法逐个添加控制点即可。

(四)例程

写一个例程,使用Curves类,实现曲线调整。

程序中定义了两个窗口,一个是图片窗口,一个是曲线窗口。

  1. /*
  2. *test_Curves.cpp
  3. *
  4. *Createdon:2016年9月11日
  5. *Author:Administrator
  6. */
  7. #include<cstdio>
  8. #include<iostream>
  9. #include"opencv2/core.hpp"
  10. #include"opencv2/imgproc.hpp"
  11. #include"opencv2/highgui.hpp"
  12. #include"Curves.hpp"
  13. usingnamespacestd;
  14. usingnamespacecv;
  15. staticstringwindow_name="Photo";
  16. staticMatsrc;
  17. staticstringcurves_window="AdjustCurves";
  18. staticMatcurves_mat;
  19. staticintchannel=0;
  20. Curvescurves;
  21. staticvoidinvalidate()
  22. {
  23. curves.draw(curves_mat);
  24. imshow(curves_window,curves_mat);
  25. Matdst;
  26. curves.adjust(src,dst);
  27. imshow(window_name,dst);
  28. inty,x;
  29. uchar*p;
  30. y=150;x=50;
  31. p=dst.ptr<uchar>(y)+x*3;
  32. cout<<"("<<int(p[2])<<","<<int(p[1])<<","<<int(p[0])<<")";
  33. y=150;x=220;
  34. p=dst.ptr<uchar>(y)+x*3;
  35. cout<<"("<<int(p[2])<<","<<int(p[1])<<","<<int(p[0])<<")";
  36. y=150;x=400;
  37. p=dst.ptr<uchar>(y)+x*3;
  38. cout<<"("<<int(p[2])<<","<<int(p[1])<<","<<int(p[0])<<")"<<endl;
  39. }
  40. staticvoidcallbackAdjustChannel(int,void*)
  41. {
  42. switch(channel){
  43. case3:
  44. curves.CurrentChannel=&curves.BlueChannel;
  45. break;
  46. case2:
  47. curves.CurrentChannel=&curves.GreenChannel;
  48. break;
  49. case1:
  50. curves.CurrentChannel=&curves.RedChannel;
  51. break;
  52. default:
  53. curves.CurrentChannel=&curves.RGBChannel;
  54. break;
  55. }
  56. invalidate();
  57. }
  58. staticvoidcallbackMouseEvent(intmouseEvent,intx,inty,intflags,void*param)
  59. {
  60. switch(mouseEvent){
  61. caseCV_EVENT_LBUTTONDOWN:
  62. curves.mouseDown(x,y);
  63. invalidate();
  64. break;
  65. caseCV_EVENT_MOUSEMOVE:
  66. if(curves.mouseMove(x,y))
  67. invalidate();
  68. break;
  69. caseCV_EVENT_LBUTTONUP:
  70. curves.mouseUp(x,y);
  71. invalidate();
  72. break;
  73. }
  74. return;
  75. }
  76. intmain()
  77. {
  78. //readimagefile
  79. src=imread("building.jpg");
  80. if(!src.data){
  81. cout<<"errorreadimage"<<endl;
  82. return-1;
  83. }
  84. //createwindow
  85. namedWindow(window_name);
  86. imshow(window_name,src);
  87. //createMatforcurves
  88. curves_mat=Mat::ones(256,256,CV_8UC3);
  89. //createwindowforcurves
  90. namedWindow(curves_window);
  91. setMouseCallback(curves_window,callbackMouseEvent,NULL);
  92. createTrackbar("Channel",curves_window,&channel,3,callbackAdjustChannel);
  93. //范例:用程序代码在RedChannel中定义一条曲线
  94. //curves.RedChannel.clearPoints();
  95. //curves.RedChannel.addPoint(Point(10,10));
  96. //curves.RedChannel.addPoint(Point(240,240));
  97. //curves.RedChannel.addPoint(Point(127,127));
  98. invalidate();
  99. waitKey();
  100. return0;
  101. }

运行效果如下:

原图:

用OpenCV实现Photoshop算法(三): 曲线调整第2张

对红色通道(Channel 1)进行曲线调整

用OpenCV实现Photoshop算法(三): 曲线调整第3张

然后,对RGB通道(Channel 0)来一个经典的S型曲线调整

用OpenCV实现Photoshop算法(三): 曲线调整第4张

呵呵,有点味道了

系列文章:

用OpenCV实现Photoshop算法(一): 图像旋转

用OpenCV实现Photoshop算法(二): 图像剪切

用OpenCV实现Photoshop算法(三): 曲线调整

用OpenCV实现Photoshop算法(四): 色阶调整

用OpenCV实现Photoshop算法(五): 亮度对比度调整

免责声明:文章转载自《用OpenCV实现Photoshop算法(三): 曲线调整》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇PerfDog可以助力高帧率游戏生态更全面发展ubuntu切换到root下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

[OpenCV] Ptr类模板

1.C++泛型句柄类我们知道在包含指针成员的类中,需要特别注意类的复制控制,因为复制指针时只复制指针中的地址,而不会复制指针指向的对象。这将导致当两个指针同时指向同一对象时,很可能一个指针删除了一对象,另一指针的用户还认为基础对象仍然存在,此时就出现了悬垂指针。 当类中有指针成员时,一般有两种方式来管理指针成员 :一是采用值型的方式管理,每个类对象都保留一...

AI佳作解读系列(二)——目标检测AI算法集杂谈:R-CNN,faster R-CNN,yolo,SSD,yoloV2,yoloV3

1 引言 深度学习目前已经应用到了各个领域,应用场景大体分为三类:物体识别,目标检测,自然语言处理。本文着重与分析目标检测领域的深度学习方法,对其中的经典模型框架进行深入分析。 目标检测可以理解为是物体识别和物体定位的综合,不仅仅要识别出物体属于哪个分类,更重要的是得到物体在图片中的具体位置。 为了完成这两个任务,目标检测模型分为两类。一类是two-sta...

图论算法》关于匈牙利算法的两三事

  这是一篇简单的匈牙利算法的理解篇,首先匈牙利算法的名字听起来就和匈牙利牛肉饭一样让人产生食欲(?)233。   好的接下来我们开始正式带大家了解什么叫匈牙利算法。   那么要了解算法的基本原理,我们先看一张图 在这张图里,我们可以清楚的看出图上的点被我们分成了两种,一种是数字,另一种是字母,并且数字与数字、字母与字母之间是没有互相连接的边的,这种点被...

0-1背包问题的分枝—限界算法

  1.分枝—限界法的基本原理 分枝—限界算法类似于回溯法,也是一种在问题的解空间树上搜索问题解的算法。但两者求解方法有两点不同:第一,回溯法只通过约束条件剪去非可行解,而分枝—限界法不仅通过约束条件,而且通过目标函数的限界来减少无效搜索,也就是剪掉了某些不包含最优解的可行解;第二,在解空间树上,回溯法以深度优先搜索,而分枝—限界法则以广度优先或...

科码先锋面试

视频面试,先做简短的自我介绍,然后结合项目和简历问了些问题 过了几道之前做的笔试题(有一道电商经典题,创建订单的过程需要操作多张表,时间过长导致数据库阻塞严重,让你优化避免这个问题 网上查到的事务调优 https://juejin.im/post/6860774571088773128)说下双向链表的结构(说了链表有结点,结点里定义了值、前置结点、后...

数据挖掘中分类算法小结_数据分析师

数据挖掘中分类算法小结_数据分析师 数据仓库,数据库或者其它信息库中隐藏着许多可以为商业、科研等活动的决策提供所需要的知识。分类与预测是两种数据分析形式,它们可以用来抽取能够描述重要数据集合或预测未来数据趋势的模型。分类方法(Classification)用于预测数据对象的离散类别(Categorical Label);预测方法(Prediction )...