用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)。還可以再增加一條曲線對 三個通道進行整體調整。 因此,對一個圖像,可以用四條曲線調整。最終的結果,是四條曲線調整後合並產生的結果。
我們先來分析對單通道一條曲線的原理,比如:對紅色通道定義一條曲線如下:
圖中,橫軸是輸入,比左到右分別表示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類,實現曲線調整。
程序中定義了兩個窗口,一個是圖片窗口,一個是曲線窗口。
[cpp] view plain copy
- /*
- * 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算法(一): 圖像旋轉
用OpenCV實現Photoshop算法(二): 圖像剪切
用OpenCV實現Photoshop算法(三): 曲線調整
用OpenCV實現Photoshop算法(四): 色階調整用OpenCV實現Photoshop算法(五): 亮度對比度調整
用OpenCV實現Photoshop算法(三): 曲線調整