Qt对图像的二值化处理

时间:2021-02-14 06:15:29

    学习一下图像处理的基础算法,写下来总结一下:

1. 灰度图像

    二值化图像的第一步是先将彩色图片转变为灰度图像,灰度图像算法如下:

我们可以通过下面几种方法,将其转换为灰度:
1.浮点算法:Gray=R*0.3+G*0.59+B*0.11
2.整数方法:Gray=(R*30+G*59+B*11)/100
3.移位方法:Gray =(R*76+G*151+B*28)>>8;
4.平均值法:Gray=(R+G+B)/3;
5.仅取绿色:Gray=G;
通过上述任一种方法求得Gray后,将原来的RGB(R,G,B)中的R,G,B统一用Gray替换,形成新的颜色RGB(Gray,Gray,Gray),用它替换原来的RGB(R,G,B)就是灰度图了。

*以上内容来自百度

2. 图像二值化

    二值化是先设定一个阈值,然后将灰度图像中每个像素的颜色值与阈值相比较,小于阈值的设置为0(黑色),大于等于的则设为255(白色);这个阈值可以设定为固定值,也可以是像素平均值,我这里使用otsu算法,查找出合适的阈值。


源码如下:

binarization.h

#ifndef CBINARIZATION_H
#define CBINARIZATION_H

#include <QImage>
#include <QColor>

using namespace std;

class CBinarization
{
public:
    CBinarization(QString imgPath);

    QImage* grayScaleImg();
    int Otsu(QImage* img);
    QImage* process(QImage* img);
    int     threshold;

private:
    QImage* _img;

    vector<int> Histogram(QImage* img);
};

#endif // CBINARIZATION_H

binarization.cpp

#include "cbinarization.h"

CBinarization::CBinarization( QString imgPath )
    : threshold(0)
{
    // 加载图片
    _img = new QImage();
    if (NULL != _img)
    {
        _img->load(imgPath);
    }
}

// 生成灰度图像
QImage* CBinarization::grayScaleImg()
{
    if (NULL == _img)
    {
        return NULL;
    }
    int nWidth = _img->width();
    int nHeight = _img->height();

    // 这里留意,我使用了QImage::Format_Grayscale8格式的图片,这种图片每个像素只用了8bit存储灰度颜色值
    QImage* grayImg = new QImage(nWidth, nHeight, QImage::Format_Grayscale8);
    QRgb rgbVal = 0;
    int grayVal = 0;

    for (int x = 0; x < nWidth; ++x)
    {
        for (int y = 0; y < nHeight; ++y)
        {
            rgbVal = _img->pixel(x, y);

            grayVal = qGray(rgbVal);    // 这里调用Qt的函数,使用(R * 11 + G * 16 + B * 5)/32的方法计算

            grayImg->setPixel(x, y, QColor(grayVal, grayVal, grayVal).rgb());
        }
    }

    return grayImg;
}

int CBinarization::Otsu(QImage* img)
{
    if (NULL == img)
    {
        return -1;
    }
    vector<int> histogram = Histogram(img);
    int total = 0;
    for (int i = 0; i != histogram.size(); ++i)
    {
        total += histogram[i];
    }

    double sum = 0.0;
    for (unsigned int i = 1; i < histogram.size(); ++i)
        sum += i * histogram[i];
    double sumB = 0.0;
    double wB = 0.0;
    double wF = 0.0;
    double mB = 0.0;
    double mF = 0.0;
    double max = 0.0;
    double between = 0.0;
    double threshold = 0.0;
    for (int i = 0; i != 256; ++i)
    {
        wB += histogram[i];
        if (wB == 0)
            continue;
        wF = total - wB;
        if (wF == 0)
            break;
        sumB += i * histogram[i];
        mB = sumB / wB;
        mF = (sum - sumB) / wF;
        between = wB * wF * (mB - mF) * (mB - mF);
        if ( between > max )
        {
            threshold = i;
            max = between;
        }
    }
    return threshold;
}

QImage* CBinarization::process(QImage* img)
{
    if (NULL == img)
    {
        return NULL;
    }
    int width = img->width();
    int height = img->height();
    int bytePerLine = img->bytesPerLine();    // 每一行的字节数
    unsigned char *data = img->bits();
    unsigned char *binarydata = new unsigned char[bytePerLine * height];

    unsigned char g = 0;
    for (int i = 0; i < height; ++i)
    {
        for (int j = 0; j < width; ++j)
        {
            g = *(data + i * bytePerLine + j);
            if(int(g) >= threshold)
            {
                binarydata[ i * bytePerLine + j] = 0xFF;
            }
            else
            {
                binarydata[ i * bytePerLine + j] = 0x00;
            }
        }
    }
    QImage *ret = new QImage(binarydata, width, height, bytePerLine, QImage::Format_Grayscale8);
    return ret;
}

std::vector<int> CBinarization::Histogram( QImage* img )
{
    unsigned char* graydata = img->bits();
    vector<int> hist(256);    // 256色
    for (int i = 0; i != img->width(); ++i)
    {
        for (int j = 0; j != img->height(); ++j)
        {
            int index = int(*graydata);
            hist[index] += 1;
            graydata += 1;    // step
        }
    }
    graydata = NULL;
    return hist;
}

main.cpp

    CBinarization* binImg = new CBinarization("D:/girl.png");
    if (NULL == binImg)
    {
        return;    // error
    }

    QImage* grayImage = binImg->grayScaleImg();

    if (NULL == grayImage)
    {
        return;    // error
    }

    grayImage->save("D:/grayimg.png");

    int threshold = binImg->Otsu(grayImage);    // 这就是计算出的阈值
    if (-1 == threshold)
    {
        return;    // error
    }

    binImg->threshold = threshold;

    QImage* binaryImg = binImg->process(grayImage);

    if (NULL == binaryImg)
    {
        return;    // error
    }

    binaryImg->save("D:/binaryimg.png");

这样就生成灰度图和二值化以后的图:

原图(来自百度,如有侵权,必删):

Qt对图像的二值化处理

灰度图:

Qt对图像的二值化处理

二值化:

Qt对图像的二值化处理


能看出来,二值化以后的图虽然奶茶杯子不见了,但是最重要的女孩还是保留的很好的Qt对图像的二值化处理


注意:

1. 代码中我生成灰度图的时候使用图片格式为 QImage::Format_Grayscale8 ,Qt助手中给出的解释The image is stored using an 8-bit grayscale format可以看出,这种图片中用8-bit存储灰度值,所以每个像素只有1字节;这里我也试过 QImage::Format_RGB888 格式(The image is stored using a 24-bit RGB format (8-8-8)),这时每个像素有3个字节,这里牵扯后面代码中有不同的地方。

2. 在Otsu生成图像直方图时Histogram函数中,因为我用的Grayscale8的格式,所以数据每次只移动1个字节(step注释处,如果是RGB888,则需要移动3个字节);

3. process函数中 img->bytesPerLine() 返回图片每一行的字节数,Grayscale8格式下与宽度相等,RGB888格式下则等于宽度的3倍;RGB888格式时,代码应修改为以下形式:

    unsigned char g = 0;
    for (int i = 0; i < height; ++i)
    {
        for (int j = 0; j < width; ++j)
        {
            g = *(data + i * bytePerLine + j * 3);    // 这里j 乘以 3
            if(int(g) >= threshold)
            {
                binarydata[ i * bytePerLine + j * 3] = 0xFF;    // 每个色值需要对3个字节赋值
                binarydata[ i * bytePerLine + j * 3 + 1] = 0xFF;
                binarydata[ i * bytePerLine + j * 3 + 2] = 0xFF;
            }
            else
            {
                binarydata[ i * bytePerLine + j * 3] = 0x00;
                binarydata[ i * bytePerLine + j * 3 + 1] = 0x00;
                binarydata[ i * bytePerLine + j * 3 + 2] = 0x00;
            }
        }
    }

每次处理3个字节的数据。

4. 两种格式下生成的图片大小也不同:

Qt对图像的二值化处理

但效果看上去是一样的。

非常感谢 keybord_dancer 的 使用Qt实现一个图像处理软件0 ,我是参考他的帖子整理出我的代码。