1. 程式人生 > >Qimage畫素級操作

Qimage畫素級操作

這篇文章主要闡述瞭如何使用Qt在畫素級別上對影象進行操作,並實現了一些影象效果,這些效果主要有:灰度,模糊,銳化,新增相框,金屬質感,改變影象飽和度,亮度還有白平衡。

scanLine 返回某一行資料,轉換為QRgb指標可進行直接有效的畫素存取操作。

介紹

文章中,我們將討論在Qt中修改影象的一些技術和演算法,在這之前,你必須知道在Qt中操作影象的一些方法。

.在Qt中有兩種表示影象的類,Qt:QImage和QPixmap,還有QBitmap來儲存單色的影象,比如遮罩,QPicture在儲存QPainter的一些操作指令。

  當我們想要在螢幕上繪製圖像的時候,最快的方法就是使用QPixmap,不過壞處就是無法訪問和修改畫素;

QImage在IO操作中有很快的速度,並且給出了訪問畫素的介面,這篇文章中我們就使用這個類。

.如果你是要處理大的圖片,比如攝像頭拍攝的照片,這種情況最好是將原圖縮小之後作為預覽圖顯示在螢幕上,除非我們允許使用者縮放影象。有兩種載入並縮放影象的方法。

。將影象載入進QImage或者QPixmap,然後調整大小:

 

[cpp] view plain copy

  1. QImage image("sample.png");  
  2. image = image.scaled(width, height);  


使用QImageReader來讀取和縮放圖片,然後再載入進QImage中。QImageReader無法將一張圖片載入進QPixmap中去,但是可以使用靜態方法 QPixmap::fromImage(QImage img)從QImage中載入進QPixmap。這個方法非常快,並且不需要載入大圖的記憶體開銷:

 

 

[cpp] view plain copy

  1. QImageReader imgReader("sample.png");  
  2. imgReader.setScaledSize(QSize(width, height));  
  3. QImage * image;  
  4. imgReader.read(image);  


。每一張圖片都是由畫素點組成,每一個畫素都有三個通道:紅,綠,藍,還有一個alpha通道來儲存透明度(JPEG格式的圖片不支援透明)。每個通道的值是0-255,三個通道都是0的話,表示黑色,都是255表示白色。這篇文章中我們用RGB來表示一種顏色,也就是三個通道的值。

 

 

。相比於一個畫素一個畫素地讀取,uchar *  QImage::scanLine(int i)可以一次讀取整行的畫素值,會更加高效,下面的例子就是按行讀取的例子,也是我們將要講的第一個例子,轉灰度圖。

 

[cpp] view plain copy

  1. QImage * MainWindow::greyScale(QImage * origin){  
  2.     QImage * newImage = new QImage(origin->width(), origin->height(), QImage::Format_ARGB32);  
  3.    
  4.     QRgb * line;  
  5.    
  6.     for(int y = 0; y<newImage->height(); y++){  
  7.         QRgb * line = (QRgb *)origin->scanLine(y);  
  8.    
  9.         for(int x = 0; x<newImage->width(); x++){  
  10.             int average = (qRed(line[x]) + qGreen(line[x]) + qRed(line[x]))/3;  
  11.             newImage->setPixel(x,y, qRgb(average, average, average));  
  12.         }  
  13.    
  14.     }  
  15.    
  16.     return newImage;  
  17. }  

 

 

灰度

我們要學習的第一個技術就是將彩色圖轉換成灰度圖,我們首先要明白的一點就是,其實標準的灰度圖就是每個畫素點的三個通道的值一樣或者近似,我們的策略就是將每個畫素的每個通道的值都調成一樣,取R,G,B值為三者的算數平均數就可以了,比如原色是RGB(169,204,69), 那麼最終的RGB就是(169+204+69)/3 = 147.

 

 

[cpp] view plain copy

  1. QImage * MainWindow::greyScale(QImage * origin){  
  2.     QImage * newImage = new QImage(origin->width(), origin->height(), QImage::Format_ARGB32);  
  3.    
  4.     QColor oldColor;  
  5.    
  6.     for(int x = 0; x<newImage->width(); x++){  
  7.         for(int y = 0; y<newImage->height(); y++){  
  8.             oldColor = QColor(origin->pixel(x,y));  
  9.             int average = (oldColor.red()+oldColor.green()+oldColor.blue())/3;  
  10.             newImage->setPixel(x,y,qRgb(average,average,average));  
  11.         }  
  12.     }  
  13.    
  14.     return newImage;  
  15. }  

 

 

原始圖

 

灰度圖

 

亮度調節

就如之前我們提到的,白色用RGB(255,255,255)表示,黑色用RGB(0,0,0)表示,所以如果我們需要提高圖片的亮度(顏色接近白色),我們需要同時增加三個通道的數值,反之就是變暗。

 

在這裡我們添加了一個函式引數來決定要提高多少亮度,如果引數是負數的話就是減少亮度了。在每個通道都加上delta值之後,需要做的就是讓它不要低於0且不要高於255.

原圖

加亮圖 Delta = 30

 

 

暖色調

當我們說一一幅暖色調的圖片的時候通常是因為這張圖色調偏黃。我們沒有黃色的通道,但是紅色和綠色混合起來就是黃色,所以我們增加這兩個通道值,然後藍色通道值不變就好了。

 

我們使用一個delta引數來決定增加紅色和綠色通道的值。一張暖色的圖片能夠給人一種復古效果,如果是有沙子的圖片,圖片將會更加生動。

 

 

[cpp] view plain copy

  1. QImage * MainWindow::warm(int delta, QImage * origin){  
  2.     QImage *newImage = new QImage(origin->width(), origin->height(), QImage::Format_ARGB32);  
  3.    
  4.     QColor oldColor;  
  5.     int r,g,b;  
  6.    
  7.     for(int x=0; x<newImage->width(); x++){  
  8.         for(int y=0; y<newImage->height(); y++){  
  9.             oldColor = QColor(origin->pixel(x,y));  
  10.    
  11.             r = oldColor.red() + delta;  
  12.             g = oldColor.green() + delta;  
  13.             b = oldColor.blue();  
  14.    
  15.             //we check if the new values are between 0 and 255  
  16.             r = qBound(0, r, 255);  
  17.             g = qBound(0, g, 255);  
  18.    
  19.             newImage->setPixel(x,y, qRgb(r,g,b));  
  20.         }  
  21.     }  
  22.    
  23.     return newImage;  
  24. }  

 

 

原圖

暖色圖 Delta = 30

 

冷色調

如果說暖色調的圖片偏黃色,那麼冷色調的圖片應該就是偏藍色了。在這個方法裡面我們只增加藍色通道的值,紅色和綠色的值不變。

冷色調的圖片可以聯想到未來,死亡或者,冷。

 

 

[cpp] view plain copy

  1. QImage * MainWindow::cool(int delta, QImage * origin){  
  2.     QImage *newImage = new QImage(origin->width(), origin->height(), QImage::Format_ARGB32);  
  3.    
  4.     QColor oldColor;  
  5.     int r,g,b;  
  6.    
  7.     for(int x=0; x<newImage->width(); x++){  
  8.         for(int y=0; y<newImage->height(); y++){  
  9.             oldColor = QColor(origin->pixel(x,y));  
  10.    
  11.             r = oldColor.red();  
  12.             g = oldColor.green();  
  13.             b = oldColor.blue()+delta;  
  14.    
  15.             //we check if the new value is between 0 and 255  
  16.             b = qBound(0, b, 255);  
  17.    
  18.             newImage->setPixel(x,y, qRgb(r,g,b));  
  19.         }  
  20.     }  
  21.    
  22.     return newImage;  
  23. }  

 

 

原圖

冷色調圖 Delta = 30

 

飽和度

我們已經說了,顏色由三個通道組成:紅,綠,藍,儘管如此,RGB不是唯一一個表示色彩的方式,在這裡,我們使用HSL格式表示色彩 - hue(色相), saturation(飽和度), lightness(明度)。

飽和的影象擁有更加生動的顏色,通常會比較好看,但是有一點要記住:不要濫用飽和度,因為很容易出現失真。

 

 

[cpp] view plain copy

  1. QImage * MainWindow::saturation(int delta, QImage * origin){  
  2.     QImage * newImage = new QImage(origin->width(), origin->height(), QImage::Format_ARGB32);  
  3.    
  4.     QColor oldColor;  
  5.     QColor newColor;  
  6.     int h,s,l;  
  7.    
  8.     for(int x=0; x<newImage->width(); x++){  
  9.         for(int y=0; y<newImage->height(); y++){  
  10.             oldColor = QColor(origin->pixel(x,y));  
  11.    
  12.             newColor = oldColor.toHsl();  
  13.             h = newColor.hue();  
  14.             s = newColor.saturation()+delta;  
  15.             l = newColor.lightness();  
  16.    
  17.             //we check if the new value is between 0 and 255  
  18.             s = qBound(0, s, 255);  
  19.    
  20.             newColor.setHsl(h, s, l);  
  21.    
  22.             newImage->setPixel(x, y, qRgb(newColor.red(), newColor.green(), newColor.blue()));  
  23.         }  
  24.     }  
  25.    
  26.     return newImage;  
  27. }  

 

 

 

原圖

 

 

飽和的圖片 Delta=30

 

模糊

這個效果相對於之前的有一點點複雜。我們會用到一個卷積濾波器,根據當前畫素的顏色和相鄰畫素的顏色來獲得一個新的顏色。同時還有一個kernel的矩陣來決定計算中相鄰畫素的影響程度。

 

原畫素會在矩陣的中心,因此我們會使用基數行的行和列。我們不會修改邊緣的畫素點,因為那些點沒有我們需要的相鄰畫素點,雖然我們也可以只使用有效的畫素點。

 

舉了例子,讓我們來看看如何計算畫素的RGB值。下面的三個舉證代表著當前畫素和鄰接畫素的RGB值,最中間的是當前畫素。

R = 20 102 99
150 200 77 
170 210 105

G = 22 33 40
17 21 33
8 15 24

B = 88 70 55
90 72 59
85 69 50

 

Kenel =  0 2 0
2 5 2
0 2 0

 

使用濾波器進行計算:

r = ( (102*2) + (150*2) + (200*5) + (77*2) + (210*2) ) / (2+2+5+2+2) = 159
g = ( (33*2) + ( 17*2) + (21*5) + (33*2) + (15*2) ) / (2+2+5+2+2) = 23
b = ( (70*2) + (90*2) + (72*5) + (59*2) + (69*2) ) / (2+2+5+2+2) = 72

 

由原始的RGB(200, 21, 72)得到了RGB(159, 23, 72).  發現最大的變化是紅色的通道,因為紅色通道的值差距最大。

 

在修改肖像照片的時候通常會使用到模糊的技術,它能後掩蓋住面板的瑕疵。

 

[cpp] view plain copy

  1. QImage * MainWindow::blur(QImage * origin){  
  2.     QImage * newImage = new QImage(*origin);  
  3.    
  4.     int kernel [5][5]= {{0,0,1,0,0},  
  5.                         {0,1,3,1,0},  
  6.                         {1,3,7,3,1},  
  7.                         {0,1,3,1,0},  
  8.                         {0,0,1,0,0}};  
  9.     int kernelSize = 5;  
  10.     int sumKernel = 27;  
  11.     int r,g,b;  
  12.     QColor color;  
  13.    
  14.     for(int x=kernelSize/2; x<newImage->width()-(kernelSize/2); x++){  
  15.         for(int y=kernelSize/2; y<newImage->height()-(kernelSize/2); y++){  
  16.    
  17.             r = 0;  
  18.             g = 0;  
  19.             b = 0;  
  20.    
  21.             for(int i = -kernelSize/2; i<= kernelSize/2; i++){  
  22.                 for(int j = -kernelSize/2; j<= kernelSize/2; j++){  
  23.                     color = QColor(origin->pixel(x+i, y+j));  
  24.                     r += color.red()*kernel[kernelSize/2+i][kernelSize/2+j];  
  25.                     g += color.green()*kernel[kernelSize/2+i][kernelSize/2+j];  
  26.                     b += color.blue()*kernel[kernelSize/2+i][kernelSize/2+j];  
  27.                 }  
  28.             }  
  29.    
  30.             r = qBound(0, r/sumKernel, 255);  
  31.             g = qBound(0, g/sumKernel, 255);  
  32.             b = qBound(0, b/sumKernel, 255);  
  33.    
  34.             newImage->setPixel(x,y, qRgb(r,g,b));  
  35.    
  36.         }  
  37.     }  
  38.     return newImage;  
  39. }  

 

 

原圖

 

模糊圖

 

銳化

像模糊中一樣,銳化一張圖片也會使用一個卷積濾波器,但是kernel矩陣是不一樣的,相鄰畫素對應的值是負的。

銳化能夠處理模糊的照片,能夠提升細節。

 

[cpp] view plain copy

  1. QImage * MainWindow::sharpen(QImage * origin){  
  2.     QImage * newImage = new QImage(* origin);  
  3.    
  4.     int kernel [3][3]= {{0,-1,0},  
  5.                         {-1,5,-1},  
  6.                         {0,-1,0}};  
  7.     int kernelSize = 3;  
  8.     int sumKernel = 1;  
  9.     int r,g,b;  
  10.     QColor color;  
  11.    
  12.     for(int x=kernelSize/2; x<newImage->width()-(kernelSize/2); x++){  
  13.         for(int y=kernelSize/2; y<newImage->height()-(kernelSize/2); y++){  
  14.    
  15.             r = 0;  
  16.             g = 0;  
  17.             b = 0;  
  18.    
  19.             for(int i = -kernelSize/2; i<= kernelSize/2; i++){  
  20.                 for(int j = -kernelSize/2; j<= kernelSize/2; j++){  
  21.                     color = QColor(origin->pixel(x+i, y+j));  
  22.                     r += color.red()*kernel[kernelSize/2+i][kernelSize/2+j];  
  23.                     g += color.green()*kernel[kernelSize/2+i][kernelSize/2+j];  
  24.                     b += color.blue()*kernel[kernelSize/2+i][kernelSize/2+j];  
  25.                 }  
  26.             }  
  27.    
  28.             r = qBound(0, r/sumKernel, 255);  
  29.             g = qBound(0, g/sumKernel, 255);  
  30.             b = qBound(0, b/sumKernel, 255);  
  31.    
  32.             newImage->setPixel(x,y, qRgb(r,g,b));  
  33.    
  34.         }  
  35.     }  
  36.     return newImage;  
  37. }  

 

 

原圖

銳化圖

 

新增相框

繪製一個相框是非常見到那的,我們只需要把相框在原圖上面繪製就可以了。這裡假設我們已經有一個和圖片一樣大小的相框了,不一樣的話要resize到一樣大。

 

[cpp] view plain copy

  1. QImage * MainWindow::drawFrame(QImage * origin){  
  2.     QImage * newImage = new QImage(* origin);  
  3.     QPainter painter;  
  4.    
  5.     painter.begin(newImage);  
  6.    
  7.     painter.drawImage(0,0, QImage(":images/frame.png"));  
  8.    
  9.     painter.end();  
  10.    
  11.     return newImage;  
  12. }  

 

 

原圖

相框

新增相框之後

 

金屬效果

這個例子中我們會結合幾種技術來獲得一種效果。下面是處理的步驟:

1.調整影象的亮度,獲得一個較暗的圖片。

2.將影象轉成灰度。

3.將灰度圖繪製在金屬的紋理上,透明度50%。

 

[cpp] view plain copy

  1. QImage * MainWindow::metal(QImage * origin){  
  2.     QImage * newImage = new QImage(":images/metal.png");  
  3.     QImage * darkImage = brightness(-100, origin);  
  4.     QImage * greyImage = greyScale(darkImage);  
  5.     QPainter painter;  
  6.    
  7.     painter.begin(newImage);  
  8.    
  9.     painter.setOpacity(0.5);  
  10.     painter.drawImage(0, 0, * greyImage);  
  11.    
  12.     painter.end();  
  13.    
  14.     delete greyImage;  
  15.     delete darkImage;  
  16.    
  17.     return newImage;  
  18. }  

 

 

 

 

原圖

 

金屬紋理

 

最終效果

 

模糊的邊框

最後再來學習一個融合的效果,這次我們想要做的是模糊圖片外延的部分,讓視線的焦點聚集在圖片的中間。

 

我們將會使用一張遮罩圖片,來決定需要模糊的部分,具體的操作步驟如下:

1.從原圖獲取一張完全模糊的圖片。

2.使用QPainter的一種融合模式,通過遮罩圖片截取出一個模糊的相框。點這裡可以學習到更多的QPainter的融合模式。

3.在原圖上繪製出模糊的邊框。

 

[cpp] view plain copy

  1. QImage * MainWindow::blurFrame(QImage * origin){  
  2.     QImage * newImage = new QImage(* origin);  
  3.     QImage * blurredImage = blur(newImage);  
  4.     QImage * mask = new QImage(":images/mask.png");  
  5.     QPainter painter;  
  6.    
  7.     //Using the composition mode SourceAtop we get a blurred frame stored in QImage mask  
  8.     painter.begin(mask);  
  9.    
  10.     painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);  
  11.     painter.drawImage(0, 0, * blurredImage);  
  12.    
  13.     painter.end();  
  14.    
  15.     //With our new frame we simply draw it over the original image  
  16.     painter.begin(newImage);  
  17.    
  18.     painter.setCompositionMode(QPainter::CompositionMode_SourceOver);  
  19.     painter.drawImage(0, 0, * mask);  
  20.    
  21.     painter.end();  
  22.    
  23.     delete mask;  
  24.     delete blurredImage;  
  25.    
  26.     return newImage;  
  27. }  

 

 

原圖

 

遮罩

 

模糊的邊框

 

最終效果

 

手機Demo

你可以下載這個手機Demo的原始碼,裡面包含了文章中的原始碼,在這個應用中,包含了3張462*260的圖片。測試應用的時候,你只要選擇其中一張並應用下面的效果就可以了。

 

總結

這篇文章應該可以成為你影象處理的入門,但是一切皆有可能。你可以修改這些方法,整合這些方法,使用其他的技術等等。想象力才是你唯一的限制。