QT - 图像处理 ( 1 ) - QImage像素级操作 - (转灰度、亮度、暖色、冷色、饱和度、模糊、锐化、金属效果 )

0、前言

这篇文章主要阐述了如何使用Qt在像素级别上对图像进行操作,并实现了一些图像效果,这些效果主要有:灰度,模糊,锐化,添加相框,金属质感,改变图像饱和度,亮度还有白平衡。

scanLine 返回某一行数据,转换为QRgb指针可进行直接有效的像素存取操作。

一、QImage、QPixmap、QPicture区别

简介:

QBitmap:存储单色的图像,比如遮罩

QPicture:在存储QPainter的一些操作指令

QPixmap:后台显示的图像,在屏幕上绘制图像最快的方法,不过坏处就是无法访问和修改像素

QImage:提供了与硬件无关的图像表示,在IO操作中有很快的速度,并且给出了访问像素的接口

区别:

1. QPixmap主要是用于绘图,针对屏幕显示而最佳化设计,QImage主要是为图像I/O、图片访问和像素修改而设计的

2. QPixmap依赖于所在的平台的绘图引擎,故例如反锯齿等一些效果在不同的平台上可能会有不同的显示效果,QImage使用Qt自身的绘图引擎,可在不同平台上具有相同的显示效果

3. 目前的Qt会把QPixmap都存储在graphics memory中,QImage是存储在客户端的,是独立于硬件的。在 X11, Mac 以及 Symbian平台上,QPixmap 是存储在服务器端,而QImage则是存储在客户端,在Windows平台上,QPixmap和QImage都是存储在客户端,并不使用任何的GDI资源

4. 由于QImage是独立于硬件的,也是一种QPaintDevice,因此我们可以在另一个线程中对其进行绘制,而不需要在GUI线程中处理,使用这一方式可以很大幅度提高UI响应速度

5. QImage可通过setPixpel()和pixel()等方法直接存取指定的像素

6. QBitmap是QPixmap的一个子类,主要用于显示单色位图。用于制作游标QCursor或笔刷QBrush等

二、图形处理

1. 处理大的图片

如果你是要处理大的图片,比如摄像头拍摄的照片,这种情况最好是将原图缩小之后作为预览图显示在屏幕上,将图像加载进QImage或者QPixmap,然后调整大小:

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

使用QImageReader来读取和缩放图片,然后再加载进QImage中。QImageReader无法将一张图片加载进QPixmap中去,但是可以使用静态方法 QPixmap::fromImage(QImage img)从QImage中加载进QPixmap。这个方法非常快,并且不需要加载大图的内存开销

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

2. 彩色图转换成灰度图

每一张图片都是由像素点组成,每一个像素都有三个通道:红,绿,蓝,还有一个alpha通道来保存透明度(JPEG格式的图片不支持透明)。每个通道的值是0-255,三个通道都是0的话,表示黑色,都是255表示白色。这篇文章中我们用RGB来表示一种颜色,也就是三个通道的值。

相比于一个像素一个像素地读取,uchar *  QImage::scanLine(int i)可以一次读取整行的像素值,会更加高效,下面的例子就是按行读取的例子,也是我们将要讲的第一个例子,转灰度图

QImage * MainWindow::greyScale(QImage * origin)
{  
  QImage * newImage = new QImage(origin->width(), origin->height(), QImage::Format_ARGB32);  
       
  QRgb * line;       
  for(int y = 0; y<newImage->height(); y++)
  {  
    QRgb * line = (QRgb *)origin->scanLine(y);  
       
    for(int x = 0; x<newImage->width(); x++)
    {
      // 灰度 - 取R,G,B值为三者的算数平均数;
      int average = (qRed(line[x]) + qGreen(line[x]) + qRed(line[x]))/3;  
      newImage->setPixel(x,y, qRgb(average, average, average));  
    }
  }   
  return newImage;  
}  

灰度

我们要学习的第一个技术就是将彩色图转换成灰度图,我们首先要明白的一点就是,其实标准的灰度图就是每个像素点的三个通道的值一样或者近似,我们的策略就是将每个像素的每个通道的值都调成一样,取R,G,B值为三者的算数平均数就可以了,比如原色是RGB(169,204,69), 那么最终的RGB就是(169+204+69)/3 = 147.

QImage * MainWindow::greyScale(QImage * origin)
{  
  QImage * newImage = new QImage(origin->width(), origin->height(), QImage::Format_ARGB32);  
       
  QColor oldColor;  
       
  for(int x = 0; x<newImage->width(); x++)
  {  
    for(int y = 0; y<newImage->height(); y++)
    {  
      oldColor = QColor(origin->pixel(x,y));  
      int average = (oldColor.red()+oldColor.green()+oldColor.blue())/3;  
      newImage->setPixel(x,y,qRgb(average,average,average));  
    }  
  }  
  return newImage;  
}  

                                                                                          原始图

                                                                                          灰度图

 3.亮度调节

就如之前我们提到的,白色用RGB(255,255,255)表示,黑色用RGB(0,0,0)表示,所以如果我们需要提高图片的亮度(颜色接近白色),我们需要同时增加三个通道的数值,反之就是变暗。

在这里我们添加了一个函数参数来决定要提高多少亮度,如果参数是负数的话就是减少亮度了。在每个通道都加上delta值之后,需要做的就是让它不要低于0且不要高于255.

                                                                                                  原图

                                                                                                  加亮图 Delta = 30

 4. 暖色调

当我们说一一幅暖色调的图片的时候通常是因为这张图色调偏黄。我们没有黄色的通道,但是红色和绿色混合起来就是黄色,所以我们增加这两个通道值,然后蓝色通道值不变就好了。

我们使用一个delta参数来决定增加红色和绿色通道的值。一张暖色的图片能够给人一种复古效果,如果是有沙子的图片,图片将会更加生动。

QImage * MainWindow::warm(int delta, QImage * origin)
{  
  QImage *newImage = new QImage(origin->width(), origin->height(), QImage::Format_ARGB32);  
       
  QColor oldColor;  
  int r,g,b;  
       
  for(int x=0; x<newImage->width(); x++)
  {  
    for(int y=0; y<newImage->height(); y++)
    {  
      oldColor = QColor(origin->pixel(x,y));  
       
      r = oldColor.red() + delta;  
      g = oldColor.green() + delta;  
      b = oldColor.blue();  
       
      //we check if the new values are between 0 and 255  
      r = qBound(0, r, 255);  
      g = qBound(0, g, 255);  
       
      newImage->setPixel(x,y, qRgb(r,g,b));  
    }  
  }  
  return newImage;  
}  

                                                                                                 原图

                                                                                                 暖色图 Delta = 30

5. 冷色调

如果说暖色调的图片偏黄色,那么冷色调的图片应该就是偏蓝色了。在这个方法里面我们只增加蓝色通道的值,红色和绿色的值不变。

冷色调的图片可以联想到未来,死亡或者,冷。

QImage * MainWindow::cool(int delta, QImage * origin)
{  
  QImage *newImage = new QImage(origin->width(), origin->height(), QImage::Format_ARGB32);  
       
  QColor oldColor;  
  int r,g,b;  
       
  for(int x=0; x<newImage->width(); x++)
  {  
    for(int y=0; y<newImage->height(); y++)
    {  
      oldColor = QColor(origin->pixel(x,y));  
       
      r = oldColor.red();  
      g = oldColor.green();  
      b = oldColor.blue()+delta;  
       
      //we check if the new value is between 0 and 255  
      b = qBound(0, b, 255);  
       
      newImage->setPixel(x,y, qRgb(r,g,b));  
    }  
  }   
  return newImage;  
}  

                                                                                                       原图

                                                                                              冷色调图 Delta = 30

6. 饱和度

我们已经说了,颜色由三个通道组成:红,绿,蓝,尽管如此,RGB不是唯一一个表示色彩的方式,在这里,我们使用HSL格式表示色彩 - hue(色相), saturation(饱和度), lightness(明度)。

饱和的图像拥有更加生动的颜色,通常会比较好看,但是有一点要记住:不要滥用饱和度,因为很容易出现失真。

QImage * MainWindow::saturation(int delta, QImage * origin)
{  
  QImage * newImage = new QImage(origin->width(), origin->height(), QImage::Format_ARGB32);  
       
  QColor oldColor;  
  QColor newColor;  
  int h,s,l;  
       
  for(int x=0; x<newImage->width(); x++)
  {  
    for(int y=0; y<newImage->height(); y++)
    {  
      oldColor = QColor(origin->pixel(x,y));  
       
      newColor = oldColor.toHsl();  
      h = newColor.hue();  
      s = newColor.saturation()+delta;  
      l = newColor.lightness();  
       
      //we check if the new value is between 0 and 255  
      s = qBound(0, s, 255);  
       
      newColor.setHsl(h, s, l);  
       
      newImage->setPixel(x, y, qRgb(newColor.red(), newColor.green(), newColor.blue()));  
    }  
  }   
  return newImage;  
}  

                                                                                                  原图

                                                                                                  饱和的图片 Delta=30

7. 模糊

这个效果相对于之前的有一点点复杂。我们会用到一个卷积滤波器,根据当前像素的颜色和相邻像素的颜色来获得一个新的颜色。同时还有一个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).  发现最大的变化是红色的通道,因为红色通道的值差距最大。

在修改肖像照片的时候通常会使用到模糊的技术,它能后掩盖住皮肤的瑕疵。

QImage * MainWindow::blur(QImage * origin)
{  
  QImage * newImage = new QImage(*origin);  
       
  int kernel [5][5]= {{0,0,1,0,0},  
                      {0,1,3,1,0},  
                      {1,3,7,3,1},  
                      {0,1,3,1,0},  
                      {0,0,1,0,0}};  
  int kernelSize = 5;  
  int sumKernel = 27;  
  int r,g,b;  
  QColor color;  
       
  for(int x=kernelSize/2; x<newImage->width()-(kernelSize/2); x++)
  {  
    for(int y=kernelSize/2; y<newImage->height()-(kernelSize/2); y++)
    {  
      r = 0;  
      g = 0;  
      b = 0;  
       
      for(int i = -kernelSize/2; i<= kernelSize/2; i++)
      {  
        for(int j = -kernelSize/2; j<= kernelSize/2; j++)
        {  
          color = QColor(origin->pixel(x+i, y+j));  
          r += color.red()*kernel[kernelSize/2+i][kernelSize/2+j];  
          g += color.green()*kernel[kernelSize/2+i][kernelSize/2+j];  
          b += color.blue()*kernel[kernelSize/2+i][kernelSize/2+j];  
        }  
      }  
       
      r = qBound(0, r/sumKernel, 255);  
      g = qBound(0, g/sumKernel, 255);  
      b = qBound(0, b/sumKernel, 255);  
       
      newImage->setPixel(x,y, qRgb(r,g,b));  
      }  
   }  
   return newImage;  
}  

                                                                                             原图

                                                                                            模糊图

8. 锐化

像模糊中一样,锐化一张图片也会使用一个卷积滤波器,但是kernel矩阵是不一样的,相邻像素对应的值是负的。

锐化能够处理模糊的照片,能够提升细节。

QImage * MainWindow::sharpen(QImage * origin)
{  
  QImage * newImage = new QImage(* origin);
  int kernel [3][3]= {{0,-1,0},  
                      {-1,5,-1},  
                      {0,-1,0}};  
  int kernelSize = 3;  
  int sumKernel = 1;  
  int r,g,b;  
  QColor color;  
   
  for(int x=kernelSize/2; x<newImage->width()-(kernelSize/2); x++)
  {  
    for(int y=kernelSize/2; y<newImage->height()-(kernelSize/2); y++)
    {  
      r = 0;  
      g = 0;  
      b = 0;  
   
      for(int i = -kernelSize/2; i<= kernelSize/2; i++)
      {  
        for(int j = -kernelSize/2; j<= kernelSize/2; j++)
        {  
          color = QColor(origin->pixel(x+i, y+j));  
          r += color.red()*kernel[kernelSize/2+i][kernelSize/2+j];  
          g += color.green()*kernel[kernelSize/2+i][kernelSize/2+j];  
          b += color.blue()*kernel[kernelSize/2+i][kernelSize/2+j];  
        }  
      }  
   
      r = qBound(0, r/sumKernel, 255);  
      g = qBound(0, g/sumKernel, 255);  
      b = qBound(0, b/sumKernel, 255);  
      newImage->setPixel(x,y, qRgb(r,g,b));  
      }  
    }  
    return newImage;  
} 

                                                                                                  原图

                                                                                                锐化图

9. 添加相框

绘制一个相框是非常 常见的,我们只需要把相框在原图上面绘制就可以了。这里假设我们已经有一个和图片一样大小的相框了,不一样的话要resize到一样大。

QImage * MainWindow::drawFrame(QImage * origin)
{  
  QImage * newImage = new QImage(* origin);  
  QPainter painter;  
  painter.begin(newImage);  // 以newImage为画布,绘制 frame.png 图形
  painter.drawImage(0,0, QImage(":images/frame.png"));  
  painter.end();  
       
  return newImage;  
}  

原图

                                                                                                  相框

                                                                                               添加相框之后

10. 金属效果

这个例子中我们会结合几种技术来获得一种效果。下面是处理的步骤:

1.调整图像的亮度,获得一个较暗的图片。

2.将图像转成灰度。

3.将灰度图绘制在金属的纹理上,透明度50%。

QImage * MainWindow::metal(QImage * origin)
{  
  QImage * newImage = new QImage(":images/metal.png");  
  QImage * darkImage = brightness(-100, origin);  
  QImage * greyImage = greyScale(darkImage);  
  QPainter painter;  
       
  painter.begin(newImage);  // 以 纹理图 为画布
       
  painter.setOpacity(0.5);  
  painter.drawImage(0, 0, * greyImage);  
       
  painter.end();  
       
  delete greyImage;  
  delete darkImage;  
       
  return newImage;  
}  

                                                                                              原图

                                                                                              金属纹理

                                                                                              最终效果

11. 模糊的边框

最后再来学习一个融合的效果,这次我们想要做的是模糊图片外延的部分,让视线的焦点聚集在图片的中间。

我们将会使用一张遮罩图片,来决定需要模糊的部分,具体的操作步骤如下:

1.从原图获取一张完全模糊的图片。

2.使用QPainter的一种融合模式,通过遮罩图片截取出一个模糊的相框。点这里可以学习到更多的QPainter的融合模式。

3.在原图上绘制出模糊的边框。

QImage * MainWindow::blurFrame(QImage * origin)
{  
  QImage * newImage = new QImage(* origin);  
  QImage * blurredImage = blur(newImage);  
  QImage * mask = new QImage(":images/mask.png");  
  QPainter painter;  
       
  //Using the composition mode SourceAtop we get a blurred frame stored in QImage mask  
  painter.begin(mask);  
       
  painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);  
  painter.drawImage(0, 0, * blurredImage);  
       
  painter.end();  
       
  //With our new frame we simply draw it over the original image  
  painter.begin(newImage);  
       
  painter.setCompositionMode(QPainter::CompositionMode_SourceOver);  
  painter.drawImage(0, 0, * mask);  
       
  painter.end();  
       
  delete mask;  
  delete blurredImage;  
       
  return newImage;  
}  

                                                                                                 原图

                                                                                                 遮罩

                                                                                                 模糊的边框

                                                                                                 最终效果

总结

这篇文章应该可以成为你图像处理的入门,但是一切皆有可能。你可以修改这些方法,整合这些方法,使用其他的技术等等。想象力才是你唯一的限制。

发布了87 篇原创文章 · 获赞 46 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/LearnLHC/article/details/96146722