Just a Computer Graphics Studio & My Life

從前有空沒事就在用影像處理軟體,很方便且輕易地按了「灰階」這個功能,軟體馬上就將影像轉換為灰階,因此會直覺以為轉換過程很簡單,直至今日,自己用OpenCV來實驗,才發覺有些細節,跟我們人的眼睛有關係。

剛好也可以來練習如何取得影像中像素RGB的值,知道這一點之後,即可拿RGB的值來做更進一步的運算,像是調整亮度、對比、r值、色相、彩度、明亮度,甚至透過統計來風格化。

很直覺地猜想,彩色轉灰接的過程應該是灰階=(紅+綠+藍)/3,其實不然。實際上人眼對綠色的亮度感最大,而對藍色最小,於是Gray = 0.299 * Red + 0.587 * Green + 0.114 * Blue才能得到比較適合人類眼睛的灰階影像,久而久之已成為影像處理界所用來彩色轉灰階的標準。

Gray = 0.299 * Red + 0.587 * Green + 0.114 * Blue中RGB的權重跟YUB顏色空間有關係,Y代表luma(跟luminance有點差異,範圍[light, dark]),U由RedYellow組成,V由BlueYellow組成,而Yellow又由RedGreen,也就是Red有三次、Green有兩次、Blue有一次的表現,我想U中和V中的顏色比重不太一樣,才會有Gray(上述)的線性組合。詳情可見:RGB and YUV Color

RGB和YUV的轉換公式如下:

  • R = Y + (1.4075 * (V – 128))
    G = Y – (0.3455 * (U – 128) – (0.7169 * (V – 128))
    B = Y + (1.7790 * (U – 128)
  • Y = R * .299 + G * .587 + B * .114
    U = R * -.169 + G * -.332 + B * .500 + 128
    V = R * .500 + G * -.419 + B * -.0813 + 128
/**
	Theme: Transform Image to Gray Level
	Compiler: Dev C++ 4.9.9.2
	Date: 100/06/06
	Author: ShengWen
	Blog: https://cg2010studio.wordpress.com/
*/
#include <cv.h>
#include <highgui.h>
#include <stdio.h>

// Image Size: 1000x669
uchar Blue[669][1000];
uchar Green[669][1000];
uchar Red[669][1000];
uchar Gray[669][1000];

int main(){
    IplImage *Image1;
    Image1 = cvLoadImage("HappyImage.jpg",1);
    /* Load Image RGB Values */
    for(int i=0;i<Image1->height;i++){
        for(int j=0;j<Image1->widthStep;j=j+3){
            Blue[i][(int)(j/3)]=Image1->imageData[i*Image1->widthStep+j];
            Green[i][(int)(j/3)]=Image1->imageData[i*Image1->widthStep+j+1];
            Red[i][(int)(j/3)]=Image1->imageData[i*Image1->widthStep+j+2];
        }
    }
    /* Implement Algorithms */
    for(int i=0;i<Image1->height;i++){
        for(int j=0;j<Image1->width;j++){
            Gray[i][j]=(uchar)(0.299*Red[i][j] + 0.587*Green[i][j] + 0.114*Blue[i][j]);
            Red[i][j]=Gray[i][j];
            Green[i][j]=Gray[i][j];
            Blue[i][j]=Gray[i][j];
        }
    }
    /* Save Image RGB Values */
    for(int i=0;i<Image1->height;i++){
        for(int j=0;j<Image1->widthStep;j=j+3){
            Image1->imageData[i*Image1->widthStep+j]=Blue[i][(int)(j/3)];
            Image1->imageData[i*Image1->widthStep+j+1]=Green[i][(int)(j/3)];
            Image1->imageData[i*Image1->widthStep+j+2]=Red[i][(int)(j/3)];
        }
    }

    cvSaveImage("HappyImage_gray.jpg",Image1);
    cvNamedWindow("Gray Level",1);
    cvShowImage("Gray Level",Image1);
    cvWaitKey(0);

    cvReleaseImage(&Image1);
    cvDestroyWindow("Gray Level");

    return EXIT_SUCCESS;
}

程式一共分為三個步驟:

  1. 將影像的RGB取出
  2. 接著轉成Gray,然後將Gray分別寫入RGB
  3. 最後儲存成影像檔

為何影像的widthStep先*i再+j呢?+j的原因是,顏色以BGRBGR…的順序儲存成數位影像,*i的原因則是,一維陣列要模擬二維影像。

灰階影像每個pixel只有一個值。

彩色影像每個pixel有三個值,以BGR的順序排列,如此一來可以加速掃描過程

初日

原始影像

首先按照程式設定0.299*Red[i][j] + 0.587*Green[i][j] + 0.114*Blue[i][j],跑出結果如下:

初日_gray

0.299*Red + 0.587*Green + 0.114*Blue

接著嘗試0.33*Red[i][j] + 0.33*Green[i][j] + 0.33*Blue[i][j],影像結果如下,似乎沒有太大的差異:

先除再加_gray

0.33*Red + 0.33*Green + 0.33*Blue

再來試(Red[i][j] + Green[i][j] + Blue[i][j])/3,影像結果如下,改變超誇張,因為uchar有溢位的狀況:

先加再除_gray

(Red + Green + Blue)/3

RGB_gray

RGB各通道的灰階影像。

參考:資料結構操作與運算-IplImage資料結構(2)程式寫法效能測試-圖片灰階處理Grayscale

廣告

Comments on: "[OpenCV] 轉換影像為灰階 (Transform Image to Gray Level)" (34)

  1. […] 1.逍遙文工作室, [OpenCV] 轉換影像為灰階 (Transform Image to Gray Level) 2.昨日, […]

    喜歡

  2. (R+G+B)/3 .. 你說的溢位是超過的意思嗎? 應該是在(R+G+B)這裡超出了char的範圍
    如果是int處理就不會有這樣的問題..只是相比會有亮度上的差異。

    喜歡

  3. […] 賓果!我們猜得果然沒有錯,原因是OpenCV中影像的資料結構是以B.G.R的順序,這個理論可以參考我這篇轉換影像為灰階 (Transform Image to Gray Level)。 […]

    喜歡

  4. 謝謝你的回覆,我本來不抱期望一年前的文章還可以得到解答!
    我想再問個問題,如果我宣告成uchar還能做運算嗎?
    我目前的工作是要將一張照片轉成黑白後,抓照片上圖案的質心

    喜歡

    • 當然可以囉!
      Gray[i][j]=(uchar)(0.299*Red[i][j] + 0.587*Green[i][j] + 0.114*Blue[i][j]);
      這一行就是以uchar型態來做運算。

      喜歡

      • 我明白了!謝謝你
        因為我之前運算時,我會cout 出來看有沒有錯誤
        但針對char 好像就無法顯示,所以我才會一直以為char不能做運算
        另外,我還有一個問題:
        就是imageData括號裡的參數是哪些?有代表什麼意思
        例如像CV_IMAGE_ELEM()裡面跟的參數我就很明白地瞭解

        喜歡

        • 文中這句話:「為何影像的widthStep要先*i再+j呢?+j的原因是,顏色以BGRBGR…的順序儲存成數位影像,*i的原因則是,一維陣列要模擬二維影像。」

          喜歡

  5. 你好
    謝謝你詳盡且用心的解說~實在收穫不少
    不過我發現以圖像大小宣告2階array實在是太佔記憶體
    所以稍微簡化了一下公式
    希望對接下來要做灰階轉換的人有幫助

    IplImage **pImage;
    uchar Blue;
    uchar Green;
    uchar Red;
    uchar Gray;

    /*************Convert color to Gray*************/
    // Load Image RGB Values
    for(int i=0;iheight;i++)
    for(int j=0;jwidthStep;j=j+3)
    {
    Blue = pImage->imageData[i*pImage->widthStep+j];
    Green = pImage->imageData[i*pImage->widthStep+j+1];
    Red = pImage->imageData[i*pImage->widthStep+j+2];

    //Grayscale conversion
    Gray=(uchar)(0.299*Red + 0.587*Green + 0.114*Blue);

    //Save Image RGB with Gray
    pImage->imageData[i*pImage->widthStep+j]=Gray;
    pImage->imageData[i*pImage->widthStep+j+1]=Gray;
    pImage->imageData[i*pImage->widthStep+j+2]=Gray;
    }

    喜歡

  6. 您好:
    一開始 “uchar Blue[669][1000]; ,,,,” 您為什麼是選擇宣告uchar?
    請問我可以改成在主程式裡宣告int Blue[669][1000] 嗎?謝謝

    喜歡

    • 宣告uchar,表示值只能介於0-255;
      若是宣告為int,代表值可能介於−2,147,483,648 to +2,147,483,647,
      因為RGB值剛好介於0-255,所以宣告成uchar是最好的作法,
      另一方面OpenCV函式多以uchar為存取對象,
      若你宣告成int不僅可能值會爆掉,
      更有可能無法使用OpenCV中的函式喔!

      喜歡

  7. 對了!如果我像你一樣在for loop裡面停止條件用<widthStep會出現存取違規,要用width才能跑。

    喜歡

  8. 請問這種東西要怎麼debug呢? 因為pixel無法像普通的變數一樣一個一個看

    int(x/3) 和 x/3 有何不同呢? 為什麼會在這裡用整數宣告?

    我把/*Save Image*/的x和y對調就出現大約1/3的影像了,而且還是彩色的 囧
    都不知道從何debug起….

    喜歡

    • debug還是可以輸出成文字來查看是否正確,當然影像檔的資料結構也要有所瞭解,就以彩色轉灰階來說,彩色影像儲存每一pixel的順序是BGR,那你在for迴圈裡就要注意,不要搞錯成RGB,至於為什麼這麼規定要查一下,我想一定有它的理由在!

      int(x/3) 和 x/3 有何不同呢?若x是int型態就沒什麼差別,出來的結果一模一樣!若x是浮點數型態,假設x=4,那麼前者結果為1,而後者結果則為1.333333…,array只允許index為int型態,所以後者會出問題喔~

      哈~其實可以玩出很多心得,若你用我的code去改,照理說應該不會有問題才是:)

      喜歡

      • 我不喜歡用別人的code去改,因為這樣感覺不扎實,不是自己寫出來的,可是往往也因為這樣搞得作業遲交 囧

        int(x/3)和"+x"一開始是我無法理解的部分,後來實在是拖了好幾天都沒成功,才對照你的code加上去。

        我在for loop裡面宣告的計數變數x和y都是int,那不是int(x/3)與x/3就完全相同?
        話說回來,既然你原本的code就是把ij都宣告成int,又為何要int(x/3)?豈不是多此一舉?

        喜歡

        • 嗯~好想法!感覺上你好像是剛學程式語言沒多久,不知道你有沒有學過基礎的「程式設計」?
          其實老實說,如果你真的想練程式基礎的話,最好是看給初學者看的入門書,因為OpenCV這個函式庫是以基礎的C/C++寫出來的,若你以初學者的身份學高等程式設計,會遇到相當大的困難喔!這麼比喻好了~想要學英語你不會拿paper來看吧?哈~不曉得你是不是研究生,因為那麼做是自討苦吃。
          再來老實說,我很多OpenCV的程式是拿範例程式來改,若要我全部自己摸索要花更多的時間呢!再者若你要進入業界以寫程式維生,幾乎不太可能讓你從零開始打造專案,我遇過的是人家寫好的函式如果是我需要的就直接理解拿來使用。
          你說的沒有錯,在此程式int(x/3)與x/3運算結果一樣,不過哪天誰在那行程式之前插入x=4.0這行指令,那麼就完蛋了~雖然說現在無關緊要,要是在發展大專案時,這種小問題可是很難抓出來的!當時我看到它這麼寫也有想了一下,就是~好吧!既然它要做保護我也不特地改掉它。如果你說這樣子效能會降低,比起未來可能在專案中出現無法預測的錯誤,損失一點效能又何妨呢?更何況今日的硬體能力可是越來越強大呢!

          喜歡

      • 我是大學部的,只在上上學期修過一學期C語言,最後雖然有教到封裝繼承那些物件導向的東西,但是並不扎實,因為到那裏開始覺得太複雜短時間內吞不下去。

        因為這學期修課都用VB做一些影像處理,不難。可是寫久了覺得VB6寫起來很彆扭,執行速度又慢難以real time,想用C語言實現之前用VB做過的東西,沒想到光是從安裝OpenCV到轉灰階就卡了兩周……

        喜歡

  9. 這樣的話不是應該會出現"存取違規"的錯誤嗎?

    喜歡

  10. 你好,我寫了半天都還是出錯,出現了奇怪的影像。可以請問一下為什麼要 int(j/3) 嗎?若板大有時間的話,小弟願意到分布資工系去請教您,謝謝!

    喜歡

    • 嗨~你怎麼知道我在分部XD?
      哈!這篇code細節很多,沒有都寫出來。
      int(j/3)之所以 j 要除以 3 是因為 j=j+3,
      一次跳三格所以除回來囉~

      如果只是簡單的問題,
      可以留言或寫信問我,
      一般而言,我天天都很忙XD~
      事業做太大了ORZ……

      想請問你~用什麼IDE和哪個OpenCV版本?
      我在想會不會是Blue、Green、Red、Gray矩陣大小沒設恰到好處?
      我那張影像大小為1000*669,
      所以才設[669][1000]喔!

      喜歡

      • VS2010pro ,OpenCV 2.3.1,code如下

        #include “stdafx.h"
        #include
        #include
        #include
        #define WIDTH 450
        #define HEIGHT 600

        IplImage *Image1, *Image2, *ImageMask;
        uchar B[WIDTH][HEIGHT],G[WIDTH][HEIGHT],R[WIDTH][HEIGHT],Gary[WIDTH][HEIGHT];

        int main()
        {
        char FileName[10]="Yang.jpg";

        Image1 = cvLoadImage(FileName,1);
        CvSize Image2Size=cvSize(WIDTH,HEIGHT);
        Image2 = cvCreateImage(Image2Size,IPL_DEPTH_8U,3); //8位元3通道的影像物件

        cvShowImage(“Show Yang Image",Image1);//秀出影像

        /*Load image RGB value*/
        for(int y=0;yheight;y++){
        for(int x=0;xwidth;x=x+3){
        B[int(x/3)][y]=Image1->imageData[y*Image1->widthStep+0+x];
        G[int(x/3)][y]=Image1->imageData[y*Image1->widthStep+1+x];
        R[int(x/3)][y]=Image1->imageData[y*Image1->widthStep+2+x];
        }
        }

        /*Implment algorithms*/
        for(int y=0;yheight;y++){
        for(int x=0;xwidth;x+=3){
        Gary[x][y]=0.33*B[x][y]+0.33*G[x][y]+0.33*R[x][y];
        B[x][y]=Gary[x][y];
        G[x][y]=Gary[x][y];
        R[x][y]=Gary[x][y];
        }
        }

        /*Save Image*/
        for(int y=0;yheight;y++){
        for(int x=0;xwidth;x+=3){
        Image2->imageData[y*Image2->widthStep+0+x]=B[x][int(y/3)];
        Image2->imageData[y*Image2->widthStep+1+x]=G[x][int(y/3)];
        Image2->imageData[y*Image2->widthStep+2+x]=R[x][int(y/3)];
        }
        }

        cvShowImage(“Show Yang Gray Image",Image2);
        cvWaitKey(0);

        cvDestroyWindow(“Show Image");

        cvReleaseImage(&Image1);
        }

        喜歡

      • 原圖可以正常show出來,所以大小應該是沒問題。

        喜歡

      • 你的/*Save Image*/有問題喔~
        y++和x+=3如果是對的,
        那迴圈中的y/3就會出錯喔~
        也就是y和x反了……
        會造成array的index雖然不會是非整數,
        但所儲存的值應該會是錯的。

        喜歡

      • 想請問兩位大師,
        我也是想把原影像跟灰階的影像分別存在 IplImage *Img, *ImgGray,
        但是在寫出灰階影像的時候,雖然為度大小沒問題,可是影像卻是不對的,
        想請教各位高手,我是哪裡寫錯了?
        感激不盡

        #include
        #include
        #include
        #include
        #include

        uchar Blue[320][240];
        uchar Green[320][240];
        uchar Red[320][240];
        uchar Gray[320][240];

        int main()
        {
        IplImage* img=cvLoadImage(“p1im4.bmp");
        cvNamedWindow(“p1im4″);
        cvShowImage(“p1im4″, img);
        cvWaitKey(0);

        IplImage* imgGray = cvCreateImage(cvSize(img->width,img->height),IPL_DEPTH_8U,3);
        printf(“img->width = %d\nimg->height = %d\n",img->width,img->height);

        for(int i=0;iheight;i++)
        {
        for(int j=0;jwidthStep;j=j+3)
        {
        Blue[i][(int)(j/3)]=img->imageData[i*img->widthStep+j];
        Green[i][(int)(j/3)]=img->imageData[i*img->widthStep+j+1];
        Red[i][(int)(j/3)]=img->imageData[i*img->widthStep+j+2];
        }
        }
        /* Implement Algorithms */

        for(int i=0;iheight;i++)
        {
        for(int j=0;jwidth;j++)
        {
        Gray[i][j]=(uchar)(0.333*Red[i][j] + 0.334*Green[i][j] + 0.333*Blue[i][j]);
        Red[i][j]=Gray[i][j];
        Green[i][j]=Gray[i][j];
        Blue[i][j]=Gray[i][j];
        }
        }

        /* Save Image RGB Values */
        for(int i=0;iheight;i++)
        {
        for(int j=0;jwidthStep;j=j+3)
        {
        imgGray->imageData[i*imgGray->widthStep+j]=Blue[i][(int)(j/3)];
        imgGray->imageData[i*imgGray->widthStep+j+1]=Green[i][(int)(j/3)];
        imgGray->imageData[i*imgGray->widthStep+j+2]=Red[i][(int)(j/3)];
        }
        }

        cvSaveImage(“p1im4Gray.bmp",imgGray);
        cvNamedWindow(“Gray Level");
        cvShowImage(“Gray Level",imgGray);
        cvWaitKey(0);
        cvReleaseImage(&imgGray);
        cvDestroyWindow(“Gray Level");

        return 0;
        }

        喜歡

      • 我已經照到上面的問題了,是我一開始RGB的維度宣告錯誤,
        還是感謝各位高手令我獲益良多!!!
        但我還是想要請教,
        該怎麼把這樣的一個灰階程式寫成函數或是副程式?
        可以拜託各位高手提供一點參考資料嗎?
        感激不盡
        我是OpenCV新手~

        喜歡

        • 哈~副程式很好寫,這跟是不是OpenCV新手比較無太大關係,只是你可能會被OpenCV的寫法給嚇到。
          只要你把參數確定好,在函式內處理那些需要的參數即可!稍微看了一下程式碼,其實很簡單,以上我的程式碼為例,就是把Image1這個變數傳入函式void imageToGray(IplImage *Image)即可!

          喜歡

發表留言

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s

標籤雲

%d 位部落客按了讚: