從前有空沒事就在用影像處理軟體,很方便且輕易地按了「灰階」這個功能,軟體馬上就將影像轉換為灰階,因此會直覺以為轉換過程很簡單,直至今日,自己用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由Red和Yellow組成,V由Blue和Yellow組成,而Yellow又由Red和Green,也就是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;
}
程式一共分為三個步驟:
- 將影像的RGB取出,
- 接著轉成Gray,然後將Gray分別寫入RGB,
- 最後儲存成影像檔。
為何影像的widthStep要先*i再+j呢?+j的原因是,顏色以BGRBGR…的順序儲存成數位影像,*i的原因則是,一維陣列要模擬二維影像。

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

彩色影像每個pixel有三個值,以BGR的順序排列,如此一來可以加速掃描過程。
首先按照程式設定0.299*Red[i][j] + 0.587*Green[i][j] + 0.114*Blue[i][j],跑出結果如下:
接著嘗試0.33*Red[i][j] + 0.33*Green[i][j] + 0.33*Blue[i][j],影像結果如下,似乎沒有太大的差異:
再來試(Red[i][j] + Green[i][j] + Blue[i][j])/3,影像結果如下,改變超誇張,因為uchar有溢位的狀況:
參考:資料結構操作與運算-IplImage資料結構(2)、程式寫法效能測試-圖片灰階處理、Grayscale。





Comments on: "[OpenCV] 轉換影像為灰階 (Transform Image to Gray Level)" (37)
[…] 1.逍遙文工作室, [OpenCV] 轉換影像為灰階 (Transform Image to Gray Level) 2.昨日, […]
讚讚
(R+G+B)/3 .. 你說的溢位是超過的意思嗎? 應該是在(R+G+B)這裡超出了char的範圍
如果是int處理就不會有這樣的問題..只是相比會有亮度上的差異。
讚讚
沒錯!因為RGB的型態是uchar,三者值的範圍0~255,
於是R+G+B值的範圍0~765,就超出uchar值的範圍,
這就是溢位(overflow)囉!
用int來處理RGB可以,
但你要注意int值的範圍,
還有int值可能為負。
話說,怎麼我剛好在看這篇做研究,你就留言給我XD~
讚讚
看這篇做研究!? 科科 也許是某種程度上的默契
讚讚
因為我的研究目標其一就是要對影像中每個pixel做處理囉~
讚讚
[…] 賓果!我們猜得果然沒有錯,原因是OpenCV中影像的資料結構是以B.G.R的順序,這個理論可以參考我這篇轉換影像為灰階 (Transform Image to Gray Level)。 […]
讚讚
謝謝你的回覆,我本來不抱期望一年前的文章還可以得到解答!
我想再問個問題,如果我宣告成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的原因則是,一維陣列要模擬二維影像。」
讚讚
你好
謝謝你詳盡且用心的解說~實在收穫不少
不過我發現以圖像大小宣告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;
}
讚讚
謝謝你的分享!讓我學到更多:)很高興我的文章幫助到你喔!
讚讚
您好:
一開始 “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中的函式喔!
讚讚
您好,我想請問一下
一開始 “uchar Blue[669][1000]; 這三行的
669跟1000是怎麼決定的嗎?!
會依照我的圖片大小要決定不同的值嗎?
讚讚
是的!我所使用的相片維度為1000*669,你可以更改為你所使用相片的維度值。
讚讚
對了!如果我像你一樣在for loop裡面停止條件用<widthStep會出現存取違規,要用width才能跑。
讚讚
請問這種東西要怎麼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到轉灰階就卡了兩周……
讚讚
放心,你會逐漸上手,這是初學者必經過程喔!加油:)
讚讚
這樣的話不是應該會出現"存取違規"的錯誤嗎?
讚讚
如果array的index為int型態,且存取array不超出既定範圍,那麼就不會有「存取違規」的問題。
讚讚
你好,我寫了半天都還是出錯,出現了奇怪的影像。可以請問一下為什麼要 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)即可!
讚讚