这篇文章主要阐述了如何使用Qt在像素级别上对图像进行操作,并实现了一些图像效果,这些效果主要有:灰度,模糊,锐化,添加相框,金属质感,改变图像饱和度,亮度还有白平衡。
介绍 文章中,我们将讨论在Qt中修改图像的一些技术和算法,在这之前,你必须知道在Qt中操作图像的一些方法。
在Qt中有两种表示图像的类,Qt:QImage和QPixmap,还有QBitmap来存储单色的图像,比如遮罩,QPicture在存储QPainter的一些操作指令。
当我们想要在屏幕上绘制图像的时候,最快的方法就是使用QPixmap,不过坏处就是无法访问和修改像素;
QImage在IO操作中有很快的速度,并且给出了访问像素的接口,这篇文章中我们就使用这个类。
如果你是要处理大的图片,比如摄像头拍摄的照片,这种情况最好是将原图缩小之后作为预览图显示在屏幕上,除非我们允许用户缩放图像。有两种加载并缩放图像的方法。
将图像加载进QImage或者QPixmap,然后调整大小:
1 2 QImage image ("sample.png" ) ;image = image.scaled(width, height);
使用QImageReader来读取和缩放图片,然后再加载进QImage中。QImageReader无法将一张图片加载进QPixmap中去,但是可以使用静态方法 QPixmap::fromImage(QImage img)从QImage中加载进QPixmap。这个方法非常快,并且不需要加载大图的内存开销:
1 2 3 4 QImageReader imgReader ("sample.png" ) ;imgReader.setScaledSize(QSize(width, height)); QImage * image; imgReader.read(image);
每一张图片都是由像素点组成,每一个像素都有三个通道:红,绿,蓝,还有一个alpha通道来保存透明度(JPEG格式的图片不支持透明)。每个通道的值是0-255,三个通道都是0的话,表示黑色,都是255表示白色。这篇文章中我们用RGB来表示一种颜色,也就是三个通道的值。
相比于一个像素一个像素地读取,uchar * QImage::scanLine(int i)可以一次读取整行的像素值,会更加高效,下面的例子就是按行读取的例子,也是我们将要讲的第一个例子,转灰度图。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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++){ 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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; }
亮度调节 就如之前我们提到的,白色用RGB(255,255,255)表示,黑色用RGB(0,0,0)表示,所以如果我们需要提高图片的亮度(颜色接近白色),我们需要同时增加三个通道的数值,反之就是变暗。
在这里我们添加了一个函数参数来决定要提高多少亮度,如果参数是负数的话就是减少亮度了。在每个通道都加上delta值之后,需要做的就是让它不要低于0且不要高于255.
暖色调 当我们说一一幅暖色调的图片的时候通常是因为这张图色调偏黄。我们没有黄色的通道,但是红色和绿色混合起来就是黄色,所以我们增加这两个通道值,然后蓝色通道值不变就好了。
我们使用一个delta参数来决定增加红色和绿色通道的值。一张暖色的图片能够给人一种复古效果,如果是有沙子的图片,图片将会更加生动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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(); r = qBound(0 , r, 255 ); g = qBound(0 , g, 255 ); newImage->setPixel(x,y, qRgb(r,g,b)); } } return newImage; }
冷色调 如果说暖色调的图片偏黄色,那么冷色调的图片应该就是偏蓝色了。在这个方法里面我们只增加蓝色通道的值,红色和绿色的值不变。
冷色调的图片可以联想到未来,死亡或者,冷。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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; b = qBound(0 , b, 255 ); newImage->setPixel(x,y, qRgb(r,g,b)); } } return newImage; }
饱和度 我们已经说了,颜色由三个通道组成:红,绿,蓝,尽管如此,RGB不是唯一一个表示色彩的方式,在这里,我们使用HSL格式表示色彩 - hue(色相), saturation(饱和度), lightness(明度)。
饱和的图像拥有更加生动的颜色,通常会比较好看,但是有一点要记住:不要滥用饱和度,因为很容易出现失真。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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(); s = qBound(0 , s, 255 ); newColor.setHsl(h, s, l); newImage->setPixel(x, y, qRgb(newColor.red(), newColor.green(), newColor.blue())); } } return newImage; }
模糊 这个效果相对于之前的有一点点复杂。我们会用到一个卷积滤波器,根据当前像素的颜色和相邻像素的颜色来获得一个新的颜色。同时还有一个kernel的矩阵来决定计算中相邻像素的影响程度。
原像素会在矩阵的中心,因此我们会使用基数行的行和列。我们不会修改边缘的像素点,因为那些点没有我们需要的相邻像素点,虽然我们也可以只使用有效的像素点。
举了例子,让我们来看看如何计算像素的RGB值。下面的三个举证代表着当前像素和邻接像素的RGB值,最中间的是当前像素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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
使用滤波器进行计算:
1 2 3 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). 发现最大的变化是红色的通道,因为红色通道的值差距最大。
在修改肖像照片的时候通常会使用到模糊的技术,它能后掩盖住皮肤的瑕疵。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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; }
锐化 像模糊中一样,锐化一张图片也会使用一个卷积滤波器,但是kernel矩阵是不一样的,相邻像素对应的值是负的。
锐化能够处理模糊的照片,能够提升细节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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; }
添加相框 绘制一个相框是非常见到那的,我们只需要把相框在原图上面绘制就可以了。这里假设我们已经有一个和图片一样大小的相框了,不一样的话要resize到一样大。
1 2 3 4 5 6 7 8 9 10 11 12 QImage * MainWindow::drawFrame(QImage * origin){ QImage * newImage = new QImage(* origin); QPainter painter; painter.begin(newImage); painter.drawImage(0 ,0 , QImage(":images/frame.png" )); painter.end(); return newImage; }
参考:https://blog.csdn.net/silangquan/article/details/41008183