用OpenCV实现Photoshop算法(三): 曲线调整
http://blog.csdn.net/c80486/article/details/52499919
系列文章:
用OpenCV实现Photoshop算法(五): 亮度对比度调整
用OpenCV实现Photoshop算法(六): 变为黑白图像
用OpenCV实现Photoshop算法(七): 调整色相饱和度
用OpenCV实现Photoshop算法(九): 高反差保留
三、曲线调整( Curves Adjustment )
曲线调整是Photoshop的最常用的重要功能之一。
网上关于曲线技术原理的材料都不完整。经过一个多月的探索、不断实验,我用OpenCV实现了曲线功能,基本算是揭开了“曲线之谜“。
(一)曲线原理
对于一个RGB图像, 可以对R, G, B 通道进行独立的曲线调整,即,对三个通道分别使用三条曲线(Curve)。还可以再增加一条曲线对 三个通道进行整体调整。 因此,对一个图像,可以用四条曲线调整。最终的结果,是四条曲线调整后合并产生的结果。
我们先来分析对单通道一条曲线的原理,比如:对红色通道定义一条曲线如下:
图中,横轴是输入,比左到右分别表示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类,实现曲线调整。
程序中定义了两个窗口,一个是图片窗口,一个是曲线窗口。
- /*
- * test_Curves.cpp
- *
- * Created on: 2016年9月11日
- * Author: Administrator
- */
- #include <cstdio>
- #include <iostream>
- #include "opencv2/core.hpp"
- #include "opencv2/imgproc.hpp"
- #include "opencv2/highgui.hpp"
- #include "Curves.hpp"
- using namespace std;
- using namespace cv;
- static string window_name = "Photo";
- static Mat src;
- static string curves_window = "Adjust Curves";
- static Mat curves_mat;
- static int channel = 0;
- Curves curves;
- static void invalidate()
- {
- curves.draw(curves_mat);
- imshow(curves_window, curves_mat);
- Mat dst;
- curves.adjust(src, dst);
- imshow(window_name, dst);
- int y, x;
- uchar *p;
- y = 150; x = 50;
- p = dst.ptr<uchar>(y) + x * 3;
- cout << "(" << int(p[2]) << ", " << int(p[1]) << ", " << int(p[0]) << ") ";
- y = 150; x = 220;
- p = dst.ptr<uchar>(y) + x * 3;
- cout << "(" << int(p[2]) << ", " << int(p[1]) << ", " << int(p[0]) << ") ";
- y = 150; x = 400;
- p = dst.ptr<uchar>(y) + x * 3;
- cout << "(" << int(p[2]) << ", " << int(p[1]) << ", " << int(p[0]) << ") " << endl;
- }
- static void callbackAdjustChannel(int , void *)
- {
- switch (channel) {
- case 3:
- curves.CurrentChannel = &curves.BlueChannel;
- break;
- case 2:
- curves.CurrentChannel = &curves.GreenChannel;
- break;
- case 1:
- curves.CurrentChannel = &curves.RedChannel;
- break;
- default:
- curves.CurrentChannel = &curves.RGBChannel;
- break;
- }
- invalidate();
- }
- static void callbackMouseEvent(int mouseEvent, int x, int y, int flags, void* param)
- {
- switch(mouseEvent) {
- case CV_EVENT_LBUTTONDOWN:
- curves.mouseDown(x, y);
- invalidate();
- break;
- case CV_EVENT_MOUSEMOVE:
- if ( curves.mouseMove(x, y) )
- invalidate();
- break;
- case CV_EVENT_LBUTTONUP:
- curves.mouseUp(x, y);
- invalidate();
- break;
- }
- return;
- }
- int main()
- {
- //read image file
- src = imread("building.jpg");
- if ( !src.data ) {
- cout << "error read image" << endl;
- return -1;
- }
- //create window
- namedWindow(window_name);
- imshow(window_name, src);
- //create Mat for curves
- curves_mat = Mat::ones(256, 256, CV_8UC3);
- //create window for curves
- namedWindow(curves_window);
- setMouseCallback(curves_window, callbackMouseEvent, NULL );
- createTrackbar("Channel", curves_window, &channel, 3, callbackAdjustChannel);
- // 范例:用程序代码在RedChannel中定义一条曲线
- // curves.RedChannel.clearPoints();
- // curves.RedChannel.addPoint( Point(10, 10) );
- // curves.RedChannel.addPoint( Point(240, 240) );
- // curves.RedChannel.addPoint( Point(127, 127) );
- invalidate();
- waitKey();
- return 0;
- }
运行效果如下:
原图:
对红色通道(Channel 1)进行曲线调整
然后,对RGB通道(Channel 0)来一个经典的S型曲线调整
呵呵,有点味道了
系列文章:
用OpenCV实现Photoshop算法(三): 曲线调整的更多相关文章
- 【VS开发】【图像处理】自动白平衡(AWB)算法---色温曲线
原文地址:http://blog.csdn.net/wzwxiaozheng/article/details/38434391 白平衡算法---色温曲线 本文大体讲解了白平衡的算法流程,适用于想了解和 ...
- opencv学习笔记(三)基本数据类型
opencv学习笔记(三)基本数据类型 类:DataType 将C++数据类型转换为对应的opencv数据类型 OpenCV原始数据类型的特征模版.OpenCV的原始数据类型包括unsigned ch ...
- 分布式共识算法 (三) Raft算法
系列目录 分布式共识算法 (一) 背景 分布式共识算法 (二) Paxos算法 分布式共识算法 (三) Raft算法 分布式共识算法 (四) BTF算法 一.引子 1.1 介绍 Raft 是一种为了管 ...
- 基于OpenCV的KNN算法实现手写数字识别
基于OpenCV的KNN算法实现手写数字识别 一.数据预处理 # 导入所需模块 import cv2 import numpy as np import matplotlib.pyplot as pl ...
- OpenCV实现KNN算法
原文 OpenCV实现KNN算法 K Nearest Neighbors 这个算法首先贮藏所有的训练样本,然后通过分析(包括选举,计算加权和等方式)一个新样本周围K个最近邻以给出该样本的相应值.这种方 ...
- java 在centos6.5+eclipse环境下调用opencv实现sift算法
java 在centos6.5+eclipse环境下调用opencv实现sift算法,代码如下: import org.opencv.core.Core; import org.opencv.core ...
- OPENCV下SIFT算法使用方法笔记
这几天继续在看Lowe大神的SIFT神作,看的眼花手脚抽筋.也是醉了!!!!实在看不下去,来点干货.我们知道opencv下自带SIFT特征检测以及MATCH匹配的库,这些库完全可以让我们进行傻瓜似的操 ...
- OpenCV中Camshitf算法学习(补充)
结合OpenCV中Camshitf算法学习,做一些简单的补充,包括: 实现全自动跟随的一种方法 参考opencv中的相关demo,可以截取目标物体的图片,由此预先计算出其色彩投影图,用于实际的目标跟随 ...
- 基于深度学习的人脸识别系统(Caffe+OpenCV+Dlib)【三】VGG网络进行特征提取
前言 基于深度学习的人脸识别系统,一共用到了5个开源库:OpenCV(计算机视觉库).Caffe(深度学习库).Dlib(机器学习库).libfacedetection(人脸检测库).cudnn(gp ...
随机推荐
- 64位系统下,一个32位的程序究竟可以申请到多少内存,4GB还是更多?(一)
前言: cpu的位是指一次性可处理的数据量是多少,1字节=8位,32位处理器可以一次性处理4个字节的数据量,依次类推.32位操作系统针对的32位的CPU设计.64位操作系统针对的64位的CPU设计.操 ...
- convolutional neural network 课程笔记
一.CNN基础 (1)CNN在CV方面的应用 image classification(图像识别).object detection(目标检测).neural style transfer(风格迁移) ...
- 数码相机常用CCD/CMOS尺寸对比
数码相机的关键元件CCD或CMOS又称为“影像传感器”,其作用相当于感光胶片.CCD尺寸越大,采集光线的效果越好,画面记录的信息就越多,保留的细节也就越丰富,所以图像更完美漂亮. CCD尺寸的大小与像 ...
- iOS开发之--storyboary下,为btn/lab/view等添加裁剪和阴影的方法
在开发过程中,很多时候我们都需要定制一个控件的属性,比如边框.阴影.圆角裁剪等,使用代码创建控件的时候,我们都知道,直接对layer图层进行各种属性的操作, self.button.layer.cor ...
- MySQL中的日期和时间函数
常用日期函数如下: 函 数 功 能 CURDATE() 获取当前日期 CURTIME() 获取当前时间 NOW() 获取当前的日期和时间 UNIX_TIMESTAMP(date) 获取日期的U ...
- Volley使用详细说明
前一篇粗略的介绍了一下Volley,并在最后附上了一段使用代码,这一篇详细的介绍一下Volley的使用.前面也说了Volley主要获取JSON对象和图片加载,这里也分为两部分介绍. 1.获取JSON对 ...
- mysql数据库导入到oracle数据库
首先,写一个cmd脚本 xx.cmd sqlldr username/password control=xx.ctl errors=10000000 direct=y 再写一个bat脚本xx.bat ...
- Openstack块存储cinder安装配置
openstack service create --name cinderv2 \ --description "OpenStack Block Storage" volumev ...
- Python全栈day24(面向对象编程作业作业_定义学校老师课程班级学生类)
面向对象作业 作业_定义学校老师课程班级学生类.py #面向对象编程作业,定义学校老师课程班级学生类 #定义几个类,尽可能定义多的数据属性及函数属性 class School: def __init_ ...
- Powershell&.NET数值取整处理
如何取一个数的整数值? 使用类型强制转换 Powershell的强制转换有2种方式,一种是直接类型强制转换,另一种是通过-as运算符进行转换 PS F:\> [int] (3 / 2) # 直接 ...