JAVA圖像處理系列(六)—— 色彩轉(zhuǎn)換RGB vs HSL

RGB色彩

RGB色彩模式是工業(yè)界的一種顏色標(biāo)準(zhǔn),是通過對紅(R)椒袍、綠(G)藕施、藍(lán)(B)三個顏色通道的變化以及它們相互之間的疊加來得到各式各樣的顏色的寇损,RGB即是代表紅、綠裳食、藍(lán)三個通道的顏色矛市。

HSL色彩( HSL color )

HSL色彩模式是工業(yè)界的一種顏色標(biāo)準(zhǔn),是通過對色相(H)诲祸、飽和度(S)浊吏、亮度(L)三個顏色通道的變化以及它們相互之間的疊加來得到各式各樣的顏色的,HSL即是代表色相救氯,飽和度卿捎,亮度三個通道的顏色。
H: Hue 色相
S:Saturation 飽和度
L :Lightness 亮度
HSL的H(hue)分量径密,代表的是人眼所能感知的顏色范圍午阵,這些顏色分布在一個平面的色相環(huán)上,取值范圍是0°到360°的圓心角享扔,每個角度可以代表一種顏色底桂。色相值的意義在于,我們可以在不改變光感的情況下惧眠,通過旋轉(zhuǎn)色相環(huán)來改變顏色籽懦。
HSL的S(saturation)分量,指的是色彩的飽和度氛魁,它用0%至100%的值描述了相同色相暮顺、亮度下色彩純度的變化。數(shù)值越大秀存,顏色中的灰色越少捶码,顏色越鮮艷,呈現(xiàn)一種從理性(灰度)到感性(純色)的變化或链。
HSL的L(lightness)分量惫恼,指的是色彩的亮度,作用是控制色彩的亮暗變化澳盐。它同樣使用了0%至100%的取值范圍祈纯。數(shù)值越小令宿,色彩越暗,越接近于黑色腕窥;數(shù)值越大粒没,色彩越亮,越接近于白色簇爆。

RGB轉(zhuǎn)HSL算法

(1)把RGB值轉(zhuǎn)成區(qū)間[0革娄,1]內(nèi)的數(shù)值。
(2)找出R,G和B中的最大值冕碟。
(3)計算亮度:L=(maxcolor + mincolor)/2
(4)如果最大和最小的顏色值相同拦惋,即表示灰色,那么S定義為0安寺,而H未定義并在程序中通常寫成0厕妖。
(5)否則,根據(jù)亮度L計算飽和度S:
????If L<0.5, S=(maxcolor-mincolor)/(maxcolor + mincolor)
????If L>=0.5, S=(maxcolor-mincolor)/(2.0-maxcolor-mincolor)
(6)計算色調(diào)H:
????If R=maxcolor, H=(G-B)/(maxcolor-mincolor)
????If G=maxcolor, H=2.0+(B-R)/(maxcolor-mincolor)
????If B=maxcolor, H=4.0+(R-G)/(maxcolor-mincolor)
????H=H*60.0,如果H為負(fù)值挑庶,則加360言秸。

HSL轉(zhuǎn)RGB算法。

(1)If S=0,表示灰色迎捺,定義R,G和B都為L.
(2)否則举畸,測試L:
????If L<0.5,temp2=L*(1.0+S)
????If L>=0.5,temp2=L+S-L*S
(3)temp1=2.0*L-temp2
(4)把H轉(zhuǎn)換到0~1。
(5)對于R,G,B凳枝,計算另外的臨時值temp3抄沮。方法如下:
????for R, temp3=H+1.0/3.0
????for G, temp3=H
????for B, temp3=H-1.0/3.0
????if temp3<0, temp3=temp3+1.0
????if temp3>1, temp3=temp3-1.0
(6)對于R,G,B做如下測試:
????If 6.0*temp3<1,color=temp1+(temp2-temp1)*6.0*temp3
????Else if 2.0*temp3<1,color=temp2
????Else if 3.0*temp3<2,
????color=temp1+(temp2-temp1)*((2.0/3.0)-temp3)*6.0
????Else color=temp1

色彩轉(zhuǎn)換JAVA實現(xiàn)代碼

public class TransformColor {
    public static final double MaxRGB = 255.0;

    public void rgbToHsl(Pixel pixel) {
        int red = pixel.red;
        int blue = pixel.blue;
        int green = pixel.green;

        double b, delta, g, max, min, r;

        double hue, saturation, luminosity;
        /*
         * Convert RGB to HSL colorspace.
         */
        r = (double) red / MaxRGB;
        g = (double) green / MaxRGB;
        b = (double) blue / MaxRGB;
        max = Math.max(r, Math.max(g, b));
        min = Math.min(r, Math.min(g, b));

        hue = 0.0;
        saturation = 0.0;
        luminosity = (min + max) / 2.0;
        delta = max - min;
        if (delta == 0.0) {
            pixel.hue = hue;
            pixel.saturation = saturation;
            pixel.luminosity = luminosity;
            return;
        }
        saturation = delta / ((luminosity <= 0.5) ? (min + max) : (2.0 - max - min));
        if (r == max)
            hue = (g == min ? 5.0 + (max - b) / delta : 1.0 - (max - g) / delta);
        else if (g == max)
            hue = (b == min ? 1.0 + (max - r) / delta : 3.0 - (max - b) / delta);
        else
            hue = (r == min ? 3.0 + (max - g) / delta : 5.0 - (max - r) / delta);
        hue /= 6.0;

        pixel.hue = hue;
        pixel.saturation = saturation;
        pixel.luminosity = luminosity;
    }

    public void hslToRgb(Pixel pixel) {
        double hue, saturation, luminosity;
        // int red, green, blue;
        double b, g, r, v, x, y, z;

        hue = pixel.hue;
        saturation = pixel.saturation;
        luminosity = pixel.luminosity;

        /*
         * Convert HSL to RGB colorspace.
         */
        v = (luminosity <= 0.5) ? (luminosity * (1.0 + saturation))
                : (luminosity + saturation - luminosity * saturation);
        if (saturation == 0.0) {
            pixel.red = (int) (MaxRGB * luminosity + 0.5);
            pixel.green = (int) (MaxRGB * luminosity + 0.5);
            pixel.blue = (int) (MaxRGB * luminosity + 0.5);
            return;
        }
        y = 2.0 * luminosity - v;
        x = y + (v - y) * (6.0 * hue - Math.floor(6.0 * hue));
        z = v - (v - y) * (6.0 * hue - Math.floor(6.0 * hue));
        switch ((int) (6.0 * hue)) {
        case 0:
            r = v;
            g = x;
            b = y;
            break;
        case 1:
            r = z;
            g = v;
            b = y;
            break;
        case 2:
            r = y;
            g = v;
            b = x;
            break;
        case 3:
            r = y;
            g = z;
            b = v;
            break;
        case 4:
            r = x;
            g = y;
            b = v;
            break;
        case 5:
            r = v;
            g = y;
            b = z;
            break;
        default:
            r = v;
            g = x;
            b = y;
            break;
        }
        pixel.red = (int) (MaxRGB * r + 0.5);
        pixel.green = (int) (MaxRGB * g + 0.5);
        pixel.blue = (int) (MaxRGB * b + 0.5);
    }
}

輔助類Pixel代碼:

import java.awt.image.*;

public class Pixel {
  public int red;
  public int green;
  public int blue;
  public int alpha=0xFF;
  public double hue;
  public double saturation;
  public double luminosity;
  private int rgb;

  public Pixel() {
  }

  public void setRGB(int rgb) {
    red = (rgb & 0x00FF0000) / 0x00010000;
    green = (rgb & 0x0000FF00) / 0x00000100;
    blue = rgb & 0x000000FF;
    alpha = (rgb >> 24)&0x0ff;
    this.rgb = rgb;
  }

  public int getRGB() {
    rgb =  alpha<<24 | 0x00010000 * red | 0x00000100 * green | blue;
    return this.rgb;
  }

  public static void setRgb(BufferedImage image, int x, int y, int red, int green, int blue) {
    int rgb = 0xFF000000 | red * 0x00010000 | green * 0x00000100 | blue;
    image.setRGB(x, y, rgb);
  }

  public static int pixelIntensity(int rgb) {
    int red = (rgb&0x00FF0000)/0x00010000;
    int green = (rgb&0x0000FF00)/0x00000100;
    int blue = rgb&0x000000FF;
    return (int) (0.299 * red + 0.587 * green + 0.114 * blue + 0.5);
  }
}

改變圖像亮度、對比度飽和度

我們可以通過把對HSL顏色的處理實現(xiàn)對圖像亮度岖瑰,對比度飽和度的調(diào)節(jié)

調(diào)節(jié)亮度叛买、色相、對比度代碼如下

    public static void modulate(double percent_hue, double percent_saturation, double percent_brightness, Pixel pixel) {

        TransformColor transform = new TransformColor();
        /*
         * Increase or decrease color brightness, saturation, or hue.
         */
        transform.rgbToHsl(pixel);
        pixel.luminosity *= (0.01) * percent_brightness;
        if (pixel.luminosity < 0.0)
            pixel.luminosity = 0.0;
        else if (pixel.luminosity > 1.0)
            pixel.luminosity = 1.0;
        pixel.saturation *= (0.01) * percent_saturation;
        if (pixel.saturation < 0.0)
            pixel.saturation = 0.0;
        else if (pixel.saturation > 1.0)
            pixel.saturation = 1.0;
        pixel.hue *= (0.01) * percent_hue;
        if (pixel.hue < 0.0)
            pixel.hue += 1.0;
        else if (pixel.hue > 1.0)
            pixel.hue -= 1.0;
        transform.hslToRgb(pixel);
    }

    public static BufferedImage hslImage(BufferedImage image, double satuPer, double huePer, double lumPer) {
        BufferedImage bimg = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
        Pixel pixel = new Pixel();

        for (int y = 0; y < bimg.getHeight(); y++) {
            for (int x = 0; x < bimg.getWidth(); x++) {
                pixel.setRGB(image.getRGB(x, y));
                modulate(huePer, satuPer, lumPer, pixel);
                bimg.setRGB(x, y, pixel.getRGB());
            }
        }
        return bimg;
    }

調(diào)節(jié)對比度代碼如下:

    public static void contrast(double percent, Pixel pixel) {
        TransformColor transform = new TransformColor();
        double alpha = percent / 100.0;

        /*
         * Enhance contrast: dark color become darker, light color become lighter.
         */
        transform.rgbToHsl(pixel);
        pixel.luminosity += alpha * (alpha * (Math.sin(Math.PI * (pixel.luminosity - alpha)) + 1.0) - pixel.luminosity);
        if (pixel.luminosity > 1.0)
            pixel.luminosity = 1.0;
        else if (pixel.luminosity < 0.0)
            pixel.luminosity = 0.0;
        transform.hslToRgb(pixel);
    }

    public static BufferedImage contractImage(BufferedImage image, double percent) {
        BufferedImage bimg = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
        Pixel pixel = new Pixel();

        for (int y = 0; y < bimg.getHeight(); y++) {
            for (int x = 0; x < bimg.getWidth(); x++) {
                pixel.setRGB(image.getRGB(x, y));
                contrast(percent, pixel);
                bimg.setRGB(x, y, pixel.getRGB());
            }
        }
        return bimg;
    }

實現(xiàn)效果

以下是對圖像亮度蹋订,飽和度率挣、對比度分別增加和減少的測試代碼:

    public static void main(String[] argv) throws IOException {
        BufferedImage img = read(new File("D:\\eclipse-workspace\\image-toolkit\\images\\girl.jpg"));
        
        BufferedImage img2 = hslImage(img, 0, 0.5, 0);
        ImageIO.write(img2, "jpeg", new File("girl-s+.jpg"));
        img2 = hslImage(img, 0, -0.5, 0);
        ImageIO.write(img2, "jpeg", new File("girl-s-.jpg"));
        
        img2 = hslImage(img, 0, 0, 0.5);
        ImageIO.write(img2, "jpeg", new File("girl-l+.jpg"));
        img2 = hslImage(img, 0, 0, -0.5);
        ImageIO.write(img2, "jpeg", new File(girl-l-.jpg"));
        
        img2 = contractImage(img, 0.7);
        ImageIO.write(img2, "jpeg", new File("girl-c+.jpg"));
        img2 = contractImage(img, -0.7);
        ImageIO.write(img2, "jpeg", new File("girl-c-.jpg"));
    }

實現(xiàn)效果

其中第一張為原始圖片,第二三張為飽和度調(diào)節(jié)圖片露戒,第四五張為亮度調(diào)節(jié)圖片椒功,第六七張為對比度調(diào)節(jié)圖片


girl.jpg
girl-s+.jpg

girl-s-.jpg

girl-l+.jpg

girl-l-.jpg

girl-c+.jpg

girl-c-.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市智什,隨后出現(xiàn)的幾起案子动漾,更是在濱河造成了極大的恐慌,老刑警劉巖撩鹿,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谦炬,死亡現(xiàn)場離奇詭異,居然都是意外死亡节沦,警方通過查閱死者的電腦和手機键思,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甫贯,“玉大人吼鳞,你說我怎么就攤上這事〗懈椋” “怎么了赔桌?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長渴逻。 經(jīng)常有香客問我疾党,道長,這世上最難降的妖魔是什么惨奕? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任雪位,我火速辦了婚禮,結(jié)果婚禮上梨撞,老公的妹妹穿的比我還像新娘雹洗。我一直安慰自己,他們只是感情好卧波,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布时肿。 她就那樣靜靜地躺著,像睡著了一般港粱。 火紅的嫁衣襯著肌膚如雪螃成。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天查坪,我揣著相機與錄音锈颗,去河邊找鬼。 笑死咪惠,一個胖子當(dāng)著我的面吹牛击吱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播遥昧,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼覆醇,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了炭臭?” 一聲冷哼從身側(cè)響起永脓,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鞋仍,沒想到半個月后常摧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年落午,在試婚紗的時候發(fā)現(xiàn)自己被綠了谎懦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡溃斋,死狀恐怖界拦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情梗劫,我是刑警寧澤享甸,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站梳侨,受9級特大地震影響蛉威,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜走哺,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一蚯嫌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧割坠,春花似錦齐帚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至敢朱,卻和暖如春剪菱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拴签。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工孝常, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚓哩。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓构灸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親岸梨。 傳聞我的和親對象是個殘疾皇子喜颁,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容