QT图像处理 -- http://developer.nokia.com/Community/Wiki/Image_editing_techniques_and_algorithms_using_Qt

 [Collapse
Article Metadata

Code Example
Source file:  Media:Image Editor.zip

Tested with
SDK: Qt SDK v1.2
Devices(s): Nokia C7-00

Compatibility
Platform(s): 
Symbian
Device(s): All with Qt

Article
Keywords: QImage, QPixmap, QPainter
Created:  Sheenmue (09 May 2012)
Last edited:  hamishwillee (11 Oct 2012)

Image editing techniques and algorithms using Qt

This article explains how to edit images at the pixel level using Qt in order to implement a number of visual effects. The effects demonstrated include: grey scale, blur, sharpen, drawing to a frame or metal, and changing the image saturation, brightness, or warmth.

Note.png
Note: This is an entry in the PureView Imaging Competition 2012Q2

Introduction

In this article we'll discuss different techniques and algorithms to modify an image using Qt, but before we continue you must know some principles to work with images.

  • There are 2 main classes to represent an image in Qt: QImage and QPixmap; we can also use QBitmap for monochrome images like masks, and QPicture stores QPainter drawing commands.
  • QPixmap is the recommended class to use when we want to draw images on the screen because it's the quickest way to do it. The problem with QPixmap is that we cannot access to an individual pixel to read it and modify it.
  • QImage is faster than QPixmap in IO operations and gives us access to the individual pixel information. It's the class that we'll use in this article to edit images.
  • If you are managing big images, like photos taken with the camera, it's recommended to work with a downsized image to show it on the screen as a preview image unless we want to allow users to zoom in the image. There are two ways to load an image from a file and downsize it:
  • Load the image into a QImage or QPixmap and resize it later:
QImage image("sample.png");
image = image.scaled(width, height);
  • Using QImageReader setting the target size before loading the image into a QImage - QImageReader can't load an image into aQPixmap but it's easy to create a QPixmap from a QImage with the static method QPixmap::fromImage(QImage img). This method is faster and you won't need the memory required to load the full size image:
QImageReader imgReader("sample.png");
imgReader.setScaledSize(QSize(width, height));
QImage * image;
imgReader.read(image);
  • Every image is made by pixels and every pixel is formed by 3 color channels: red, green and blue, and an alpha channel that contains the transparency value (JPG and other image formats don't support transparency). These channels have values between 0 and 255 and black color is formed when these 3 color channels are 0 while white is represented when the value of the 3 channels is 255. To represent a color in this article we'll refer to it as RGB(red, green, blue) being red, green and blue the value of these 3 channels.
  • To simplify the code below we are reading pixel by pixel, but reading a full line with the method QImage::scanLine(int i) is more efficient. This is an example on how the code would look like reading pixels line by line with the first technique we'll describe: converting an image to grey scale.
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;
}


Grey Scale

First technique we'll see is converting an image to grey scale. We must know that grey tones have similar if not the same value in the 3 color channels, so our strategy will be to modify every pixel and make the RGB channels have the same value. We'll determine this value with the arithmetic mean of the 3 channels so if the original color is RGB(169, 204, 69) the new color will be RGB(147, 147, 147) - (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;
}


Brightness

As we said in the introduction of this article, white is represented as RGB(255, 255, 255) while black is RGB(0, 0, 0), so if we want to increase the brightness (the color will be closer to white) we need to increment the value of the 3 channels and decrease it to get a darker image.

In this case we'll use a delta parameter to determine how much we'll increase all the channels, so if delta is negative we'll get a dark image. After adding delta we only need to check the new values are between 0 and 255.

QImage * MainWindow::brightness(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() + delta;
 
            //we check if the new values are between 0 and 255
            r = qBound(0, r, 255);
            g = qBound(0, g, 255);
            b = qBound(0, b, 255);
 
            newImage->setPixel(x,y, qRgb(r,g,b));
        }
    }
 
    return newImage;
}


Warm

When we talk about a warm image that's because colors tend to yellow. We don't have a yellow channel, but however yellow is the result of mixing red and green so to get a warm image we'll increase these two channels leaving blue channel with the same value.

We'll use a delta parameter to determine how much we'll increase red and green channels. With a warmer image we can get a retro effect and for example in images where there's sand we'll get a more vivid image.

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;
}


Cool

If colors in a warm image tend to yellow, with cool images the most intense color is blue. In our method we'll increase the blue channel with a delta value leaving red and green channels unmodified.

Cool images can be related with future, death or simply cold.

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;
}


Saturation

We've said colors are formed by 3 channels: red, green and blue. Even if that's true, RGB isn't the only way to represent a color and in this case we'll use the HSL format - hue, saturation, lightness. We only need to increase saturation channel to get our saturated image.

Saturated images have more vivid colors and use to be more spectacular, but it's important not to abuse with the saturation or we'll lose the fidelity of the original image.

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;
}


Blur

This is a bit more complex than what we have made until now. To blur an image we'll use a convolution filter that creates the new color of every pixel according to its original color and the color of the adjacent pixels. There's a matrix called kernel that determines how much influence will have each adjacent pixel in this calculation.

Our original pixel will be in the center of this matrix and for that reason we'll use matrices with an odd number of rows and columns. Note that we won't modify the pixels that are in the borders of the image because we don't have all the adjacent pixels we need, although we could modify the method to use only the available pixels.

Let's see an example on how we calculate the RGB value for a pixel. These are the matrices that represent the red, green and blue channels of our pixel and its adjacent pixels - our pixel is in the center of the matrix:

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

And our kernel is:

Kernel = 0 2 0
         2 5 2
         0 2 0

And this is how we calculate our new value after applying the filter:

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

So we went from RGB(200, 21, 72) to RGB(159, 23, 72). You can notice that the biggest change was in the red channel where there's a big difference in the value of the adjacent pixels.

One of the cases when we'll want to use blur with an image is when we have portraits. Blur will help us to hide skin flaws.

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;
}


Sharpen

To sharpen an image we'll use again a convolution filter as the one we used to blur it, but with a different kernel. Now, the kernel will have negative values in the adjacent pixels.

Sharpening is useful with blurry images, it helps to improve the level of detail.

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;
}


Drawing a Frame

Drawing a frame is quite simple, we only need to draw the frame over our original image. In our method we suppose we have a picture with the same size of our frame, but we could need to resize the frame or even use 4 images for the 4 borders to keep the thickness of the frame when we resize it.

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;
}


Drawing on Metal

This is a sample on how we can combine several techniques to get an effect. These are the steps we follow to get this effect:

  1. Adjust the brightness of the image to get a darker picture.
  2. Convert the result to grey scale.
  3. We draw the grey picture over a metal texture applying an opacity of 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;
}


Blurred Frame

To finalize, we'll see another combined effect. This time we want to blur the exterior part of the picture so the focus is in the center.

We'll use an image that serves as mask to determine what part of the image will be blurred. These are the steps we take:

  1. We get a full blurred image from our original picture.
  2. Using one of QPainter's composition modes we get a blurred frame with the shape of our mask. You can learn more about QPainter's composition modes in QPainter documentation.
  3. We draw our blurred frame over the original image.


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;
}


Sample App

You can download the source code of a sample app with all the methods we have seen in this article. In this app it's included 3 sample images with the same size, 462x260, to test the different methods. You simply select one of these pictures and touch one of the buttons to apply the desired effect.

Download: File:Image Editor.zip

Image editor screenshot

Summary

This article should serve you as an entry point to image editing, but possibilities are endless. You can modify these methods, combine them, use other techniques... imagination is your only limit.

Comments

Daliusd - Grey scale

Overall nice listing of algorithms but my recommendation for grey scale algorithm is to use luma formula Y = 0.2126 R + 0.7152 G + 0.0722 B from  Luma instead of giving similar weight to each color.

daliusd 15:55, 10 May 2012 (EEST)

Hamishwillee - Awesome

Hi Sheenmue,

Great article. I like in particular how you've kept it focussed with clear code samples and good comparative images. This would make it much easier for anyone who wanted to create an image editing app.

Given the aim of this code is "re-use" I might have suggested that you did not make this compromise: "To simplify the code below we are reading pixel by pixel, but reading a full line with the method QImage::scanLine(int i) is more efficient.". That said, if people understand the first code, it should be possible for them to adapt it to reading a while line.

FYI, I subedited to add more links to reference documentation, and added MeeGo category - I know its untested, but there is nothing here that shouldn't work.

Regards

Hamish

hamishwillee 09:09, 11 May 2012 (EEST)

Chintandave er - nice article !

Hi Sheenmue,

Nice article. Thanks for that.

Chintan.

Chintandave er 09:25, 11 May 2012 (EEST)

Sheenmue -

Thanks for you modifications Hamishwillee, it's the first time I write an article in the wiki and I'm not used to the conventions.

I really wanted to keep the code simple because even if this code can be reused, as Daliusd pointed with his comment there are many ways to do the same thing and it can be quite more complex, and I wanted this article could serve as an entry point to image editing so I prefer the code is easy to understand than efficient. For example, I remember the first time I made a game, it was after my first year in university and I knew how to code but I didn't know how a game worked. It was thanks to the documentation I found in the old Siemens Mobile developer website and Forum Nokia - yeah, it was quite some time ago :) - that I understood it, a game loop with 3 steps: read the input from the user, update the game, draw the result on the screen. After that I could write my own games, and I wanted that with the article for people that don't know how image editing works.

But anyway, your suggestion gave me an idea and I have written an example on how to modify a method to read a full line from an image.

Sheenmue 18:22, 11 May 2012 (EEST)

Hamishwillee - Cool!

Hi Sheenmue

You're welcome. Thanks for explaining your reasoning - its certainly a good argument for keeping things simple.

One thing that did occur to me is that if you do this calculation in a UI thread it could make the UI unresponsive. It may be worth putting in a comment on this and linking to topics on how to do the image manipulation in a worker thread (like Multi-Threaded Image Processing using Qt Camera). I believe you can do your processing in a QImage on another thread, but not in a QPixmap. Anyway, just something for you to think about.

Regards Hamish

Sheenmue -

Yes, it's possible using QPainter with QImage in a different thread, with QPixmap it's not recommended or you can end up with a blank image if UI thread is updated at the same time -if it's not updated at the same time it should work fine too, but using standard UI controls that's hard to manage-.

This weekend I'll try to update the sample app processing the images in a secondary thread and add the advice in the introduction section. I'll also add an animation while the image is being updated.

Sheenmue 19:02, 17 May 2012 (EEST)

Hamishwillee - Thanks!

Sounds great, if you have time. If you don't, then given the nature of your article, just treating multithreading as a separate issue (ie explaining the need and linking to instructions) would be sufficient.

FYI, Tuesday is deadline for next selection round.

hamishwillee 08:25, 25 May 2012 (EEST)

This page was last modified on 11 October 2012, at 04:17.
549 page views in the last 30 days.
Nokia Developer aims to help you create apps and publish them so you can connect with users around the world.

京ICP备05048969号  © Copyright Nokia 2013 All rights reserved
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值