MENU

分类 计算机视觉 下的文章

基于Qt的图像处理算法和实现——索贝尔边缘检测

索贝尔算子(Sobel operator)主要用作边缘检测,在技术上,它是一离散性差分算子,用来运算图像亮度函数的灰度之近似值。在图像的任何一点使用此算子,将会产生对应的灰度矢量或是其法矢量

Sobel卷积因子为:

$$\begin{matrix}
\left [ \begin{matrix}
-1 & 0 & +1\\
-2 & 0 & +2 \\
-2 & 0 & +1
\end{matrix} \right ]& \left [ \begin{matrix}
+1 & +2 & +1\\
0 & 0 & 0 \\
-1 & -2 & -1
\end{matrix}\right ]\\
Gx & Gy
\end{matrix}$$

该算子包含两组3x3的矩阵,分别为横向及纵向,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。如果以A代表原始图像,GxGy分别代表经横向及纵向边缘检测的图像灰度值,其公式如下:

$$\begin{align*}
G_{x} &= \left [ \begin{matrix}
-1 & 0 & +1\\
-2 & 0 & +2\\
-1 & 0 & +1
\end{matrix} \right ]*A
\\
G_{y} &= \left [ \begin{matrix}
+1 & +2 & +1\\
0 & 0 & 0\\
-1 & -2 & -1
\end{matrix} \right ]*A
\end{align*}$$

图像的每一个像素的横向及纵向灰度值通过以下公式结合,来计算该点灰度的大小:

$$G = \sqrt{ G_{x}^{2} + G_{y}^{2} }$$

通常,为了提高效率 使用不开平方的近似值:

$$\left | G \right | = \left | G_{x} \right | + \left | G_{y} \right |$$

如果梯度G大于某一阀值 则认为该点(x,y)为边缘点。

然后可用以下公式计算梯度方向:

$$\theta = arctan(\frac{G_{y}}{G_{x}})$$

Sobel算子根据像素点上下、左右邻点灰度加权差,在边缘处达到极值这一现象检测边缘。对噪声具有平滑作用,提供较为精确的边缘方向信息,边缘定位精度不够高。当对精度要求不是很高时,是一种较为常用的边缘检测方法。

算法实现:

QImage Tools::SobelEdge(const QImage &origin)
{
    double *Gx = new double[9];
    double *Gy = new double[9];

    /* Sobel */
    Gx[0] = 1.0; Gx[1] = 0.0; Gx[2] = -1.0;
    Gx[3] = 2.0; Gx[4] = 0.0; Gx[5] = -2.0;
    Gx[6] = 1.0; Gx[7] = 0.0; Gx[8] = -1.0;

    Gy[0] = -1.0; Gy[1] = -2.0; Gy[2] = - 1.0;
    Gy[3] = 0.0; Gy[4] = 0.0; Gy[5] = 0.0;
    Gy[6] = 1.0; Gy[7] = 2.0; Gy[8] = 1.0;

    QRgb pixel;
    QImage grayImage = GreyScale(origin);
    int height = grayImage.height();
    int width = grayImage.width();
    QImage newImage = QImage(width, height,QImage::Format_RGB888);

    float sobel_norm[width*height];
    float max = 0.0;
    QColor my_color;

    for (int x=0; x<width; x++)
    {
        for( int y=0; y<height; y++)
        {
            double value_gx = 0.0;
            double value_gy = 0.0;

            for (int k=0; k<3;k++)
            {
                for(int p=0; p<3; p++)
                {
                    pixel=grayImage.pixel(x+1+1-k,y+1+1-p);
                    value_gx += Gx[p*3+k] * qRed(pixel);
                    value_gy += Gy[p*3+k] * qRed(pixel);
                }
//                sobel_norm[x+y*width] = sqrt(value_gx*value_gx + value_gy*value_gy)/1.0;
                sobel_norm[x+y*width] = abs(value_gx) + abs(value_gy);

                max=sobel_norm[x+y*width]>max ? sobel_norm[x+y*width]:max;
            }
        }
    }

    for(int i=0;i<width;i++){
        for(int j=0;j<height;j++){
            my_color.setHsv( 0 ,0, 255-int(255.0*sobel_norm[i + j * width]/max));
            newImage.setPixel(i,j,my_color.rgb());
        }
    }
    return newImage;
}

处理效果:

[caption id="attachment_1689" align="aligncenter" width="225"] 原图[/caption]

[caption id="attachment_1690" align="aligncenter" width="225"] 处理后[/caption]

 

 

基于Qt的图像处理算法和实现——拉普拉斯锐化

锐化的目的是为了突出图像的边缘和细节,图像的二阶导数反映了图像的细节。

当邻域中心像素灰度低于它所在的领域内其它像素的平均灰度时,此中心像素的灰度应被进一步降低,当邻域中心像素灰度高于它所在的邻域内其它像素的平均灰度时,此中心像素的灰度应被进一步提高,以此实现图像的锐化处理。

操作原理

  1. 基于一阶微分的图像增强原理

对于二元函数的一阶偏微分微分表示如下:

$$\frac{\partial f}{\partial x} = f(x,y) - f(x-1, y) $$

$$\frac{\partial f}{\partial y} = f(x,y) - f(x, y-1)$$

定义二元函数的微分为:

$$\begin{align*}
\bigtriangledown f &= \frac{\partial f}{\partial x} + \frac{\partial f}{\partial y} \\
&= f(x,y)-f(x-1,y)+f(x,y)-f(x,y-1)\\
&=2f(x,y)-f(x-1,y)-f(x,y-1)
\end{align*}$$

这就描述了图像中一个像素点(x,y)的一阶微分变化。简单的总结为一个算子的话就是一个2*2的矩阵如下:

$$\begin{matrix}
-1 & 2 \\
0 & -1
\end{matrix}$$

将用这个算子处理后的值与(x,y)处原有的值相加,就可以将使这种变化更明显,也就是放大这种变化,从而达到图像锐化的目的。

2. 基于二阶微分的图像增强处理

类似上面的推导过程我们得到二阶微分:

$$\bigtriangledown ^2 f=f(x+1,y)+f(x-1,y)+f(x,y+1)+f(x,y-1)-4f(x,y)$$

从而得到拉普拉斯算子:

$$\begin{matrix}
0 & -1 & 0\\
-1 & 4 & -1\\
0 & -1 & 0
\end{matrix}$$

用这个算子对图像的每个像素进行处理得到的是图像的变化程度,应该将这种变化叠加到原图像:

$$g(x,y)=
\left\{\begin{matrix}
f(x,y)-\bigtriangledown ^{2} f(x,y), & \text{ A }<0 \\
f(x,y)+\bigtriangledown ^{2} f(x,y), & \text{ A }>0
\end{matrix}\right.$$

上式中A为拉普拉斯掩模中心系数,如上面的拉普拉斯算子中该系数为4.

注:每个像素R、G、B应该分别做如上处理。

算法实现(Qt/C++):

QImage LaplaceSharpen(const QImage &origin)
{
    int width = origin.width();
    int height = origin.height();
    QImage newImage = QImage(width, height,QImage::Format_RGB888);
    int window[3][3] = {0,-1,0,-1,4,-1,0,-1,0};

    for (int x=1; x<width; x++)
    {
        for(int y=1; y<height; y++)
        {
            int sumR = 0;
            int sumG = 0;
            int sumB = 0;

            //对每一个像素使用模板
            for(int m=x-1; m<= x+1; m++)
                for(int n=y-1; n<=y+1; n++)
                {
                    if(m>=0 && m<width && n<height)
                    {
                        sumR += QColor(origin.pixel(m,n)).red()*window[n-y+1][m-x+1];
                        sumG += QColor(origin.pixel(m,n)).green()*window[n-y+1][m-x+1];
                        sumB += QColor(origin.pixel(m,n)).blue()*window[n-y+1][m-x+1];
                    }
                }


            int old_r = QColor(origin.pixel(x,y)).red();
            sumR += old_r;
            sumR = qBound(0, sumR, 255);

            int old_g = QColor(origin.pixel(x,y)).green();
            sumG += old_g;
            sumG = qBound(0, sumG, 255);

            int old_b = QColor(origin.pixel(x,y)).blue();
            sumB += old_b;
            sumB = qBound(0, sumB, 255);


            newImage.setPixel(x,y, qRgb(sumR, sumG, sumB));
        }
    }
    return newImage;
}

效果截图:

[caption id="attachment_1685" align="aligncenter" width="650"] 原图[/caption]

[caption id="attachment_1686" align="aligncenter" width="650"] 处理后[/caption]

 

机器学习环境搭建:openSUSE 42.2+TensorFlow

最近在做图像识别需要用到Tensorflow,这里记录下安装的过程。因为安装到现在已经比较久远,下面有些地方可能有些许丢失,如果出现问题注意仔细查看报错信息。

编译安装会比下载通用的二进制包具有更好的性能。因为自己的笔记本电脑上没有nVidia的显卡,所以没有启动CUDA支持,下面的安装都是针对Python3.(我还是个小孩子,没赶上Python2)

1.安装前的准备

最好提前准备下以下几个软件包

python-devel

python3-devel

python-numpy

python-wheel

其中 python-numpy 和 python-wheel 可以直接在 pip 中安装 numpy 和 wheel

除此还需要Java的环境,具体配置过程请参考之前的这篇文章《openSUSE配置 Oracle JDK 开发环境

2.安装Google构建工具Bazel

Bazel是Google官方开源的一个构建工具,用来配合Google的软件开发模式。

从Github上下载 Bazel 最新的 Linux Release 版本:

[url href=“https://github.com/bazelbuild/bazel/releases/download/0.4.5/bazel-0.4.5-installer-linux-x86_64.sh”]下载 Bazel Linux Release 0.4.5[/url]

下载完成后执行:

chmod +x bazel-0.4.5-installer-linux-x86_64.sh
./bazel-0.4.5-installer-linux-x86_64.sh --user

安装完成后会有类似这样的提示:

Bazel is now installed!

Make sure you have "/home/jjt/bin" in your path. You can also activate bash
completion by adding the following line to your ~/.bashrc:
source /home/jjt/.bazel/bin/bazel-complete.bash

See http://bazel.io/docs/getting-started.html to start a new project!

然后在 ~/.bashrc中追加:

source /home/jjt/.bazel/bin/bazel-complete.bash
export PATH=$PATH:/home/jjt/.bazel/bin

之后执行一下:

source ~/.bashrc

3.编译安装Tensorflow

首先从github上克隆TensorFlow最新的代码:

git clone https://github.com/tensorflow/tensorflow

代码下载完毕之后,进入tensorflow主目录,执行:

./configure

这里会有很多提示,除了几个选择Yes或No的选项外,其他直接回车就好。这里我没有启用CUDA、Hadoop File System等特性。

执行一下TensorFlow官方文档里的例子,看看能不能成功,成功的话会有一堆数据输出。

bazel-bin/tensorflow/cc/tutorials_example_trainer
000003/000006 lambda = 1.841570 x = [0.669396 0.742906] y = [3.493999 -0.669396]
000006/000007 lambda = 1.841570 x = [0.669396 0.742906] y = [3.493999 -0.669396]
000009/000006 lambda = 1.841570 x = [0.669396 0.742906] y = [3.493999 -0.669396]
000009/000004 lambda = 1.841570 x = [0.669396 0.742906] y = [3.493999 -0.669396]
000000/000005 lambda = 1.841570 x = [0.669396 0.742906] y = [3.493999 -0.669396]
000000/000004 lambda = 1.841570 x = [0.669396 0.742906] y = [3.493999 -0.669396]

输出类似上面这样。

这时我们还不能直接在Python中import,继续下面的操作:

bazel build -c opt //tensorflow/tools/pip_package:build_pip_package
bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg
sudo pip3 install /tmp/tensorflow_pkg/tensorflow-1.1.0rc0-cp34-cp34m-linux_x86_64.whl

至此大功告成,可以在Python3中愉快地使用了!

基于Qt的图像处理算法和实现——线性灰度变换

灰度的线性变换使用的变换函数是一个一维的线性函数:y = ax + b

x为原灰度值,y即得到的新灰度。

当a>1时,输出图像的对比度将增大;

当a<1时,输出图像的对比度将减小;

当a=1时且b≠0时,变换将使灰度值上移或下移,其效果是使整个图像变亮或变暗(我们在《基于Qt的图像处理算法和实现——灰度图像、冷暖色调、亮度》这篇文章中改变图像亮度时就是直接对各颜色分量的值进行加减操作);

当a<0时,暗区域将变亮,亮区域将变暗。

特殊情况:a=01,d=255时,输出图像的灰度相反。

示例代码

下面的代码针对所有类型的图像文件,不仅仅是位图,所以第一步操作是根据RGB值计算灰度值。对于彩色转灰度,有一个很著名的心理学公式:

$$Gray = R*0.299 + G*0.587 + B*0.114$$

至于用这个公式而不是直接取平均值,是因为人眼对RGB颜色的感知程度并不相同,所以在转换的时候应该分别为RGB设置不同的权重。至于其背后的详细说明及物理上的原理,请移步知乎的这个帖子:

传送门:知乎 RGB 转为灰度值的心理学公式 Gray = 0.30 * R + 0.59 * G + 0.11 * B 是怎么来的?

考虑到一幅图像的每个像素都需要使用上面的公式,计算量比较大,我们这里采用了:

$$ Gray = (R*299 + G*587 + B*114 + 500) / 1000$$

这个公式,注意后面那个除法是整数除法,所以需要加上500来实现四舍五入。

/*****************************************************************************
 *                           线性灰度变换 y = ax + b
 * **************************************************************************/
QImage Tools::LinearLevelTransformation(const QImage &origin, double _a, double _b)
{
    QImage *newImage = new QImage(origin.width(), origin.height(),
                                   QImage::Format_ARGB32);
    QColor oldColor;
    int grayLevel = 0;

    for (int x=0; x<newImage->width(); x++) {
        for (int y=0; y<newImage->height(); y++) {
            oldColor = QColor(origin.pixel(x,y));
            grayLevel = (oldColor.red()*299+oldColor.green()*587+oldColor.blue()*114+500)/1000;
            int _y = _a*grayLevel + _b;
            // Make sure that the new values are between 0 and 255
            _y = qBound(0, _y, 255);

            newImage->setPixel(x,y,qRgb(_y,_y,_y));
        }
    }
//    qDebug()<<"a:"<<_a<<"\tb:"<<_b;

    return *newImage;
}

注意,变换之后得到的灰度应该控制在 0~255之间。

效果截图

[caption id="attachment_1672" align="aligncenter" width="985"] 线性灰度变换[/caption]

上图中是 a =1.2, b=0时的效果

基于Qt的图像处理算法和实现——绘制图像直方图

关于图像直方图的介绍可以看我以前的一篇文章《图像处理中的直方图均衡化

这篇文章介绍了使用Qt绘制图像的直方图,包括灰度直方图和各颜色分量的直方图。

具体算法实现

#ifndef HISTOGRAM_H
#define HISTOGRAM_H

#include <QWidget>
#include <QLabel>
#include <QPainter>
#include <QDebug>

class Histogram : public QLabel
{
public:
    Histogram(QWidget* parent = 0);
    Histogram(QWidget*, Histogram*);

    void computeHstgrm(QImage img);
    void paintEvent(QPaintEvent *e);
    void drawBwHstgrm(int xBase, int yBase, int height);
    void drawRedHstgrm(int xBase, int yBase, int height);
    void drawGreenHstgrm(int xBase, int yBase, int height);
    void drawBlueHstgrm(int xBase, int yBase, int height);
    int getBwHstgrm(int index);
    int getRedHstgrm(int index);
    int getGreenHstgrm(int index);
    int getBlueHstgrm(int index);

private:
    // index 0 to 255 => count of image's pixels for this value
    // index 256 => maximum value
    // index 257 => total value of the dark component
    // index 258 => total value of the light component
    int bwHstgrm[259];

    // index 0 to 255 => count of image's pixels for this value
    // index 256 => maximum value
    // index 257 => total value of the component
    int redHstgrm[258];
    int greenHstgrm[258];
    int blueHstgrm[258];
};

#endif // HISTOGRAM_H
#include "histogram.h"

#include <sstream>
#include <iostream>


Histogram::Histogram(QWidget * parent) : QLabel(parent)
{
    for(int i = 0;i<256;i++)
    {
        bwHstgrm[i] = 0;
        redHstgrm[i] = 0;
        greenHstgrm[i] = 0;
        blueHstgrm[i] = 0;
    }

    bwHstgrm[256] = -1;
    redHstgrm[256] = -1;
    greenHstgrm[256] = -1;
    blueHstgrm[256] = -1;

    redHstgrm[257] = 0;
    greenHstgrm[257] = 0;
    blueHstgrm[257] = 0;

    bwHstgrm[257] = 0;
    bwHstgrm[258] = 0;
}



Histogram::Histogram(QWidget * parent, Histogram * hstgrm) : QLabel(parent)
{
    for(int i = 0;i<258;i++)
    {
        bwHstgrm[i] = hstgrm->bwHstgrm[i];
        redHstgrm[i] = hstgrm->redHstgrm[i];
        greenHstgrm[i] = hstgrm->greenHstgrm[i];
        blueHstgrm[i] = hstgrm->blueHstgrm[i];
    }

    bwHstgrm[258] = hstgrm->bwHstgrm[258];
}



void Histogram::computeHstgrm(QImage img)
{
    if (!img.isNull())
    {
        for(int i = 0;i<img.height();i++)
        {
            for(int j = 0;j<img.width();j++)
            {
                int bwValue = qGray(img.pixel(j, i));

                int redValue = qRed(img.pixel(j, i));
                int greenValue = qGreen(img.pixel(j, i));
                int blueValue = qBlue(img.pixel(j, i));

                bwHstgrm[bwValue]++;
                redHstgrm[redValue]++;
                greenHstgrm[greenValue]++;
                blueHstgrm[blueValue]++;
            }
        }

        for(int i = 0;i<256;i++)
        {
            // maximum values
            if (bwHstgrm[256] < bwHstgrm[i])
                bwHstgrm[256] = bwHstgrm[i];

            if (redHstgrm[256] < redHstgrm[i])
                redHstgrm[256] = redHstgrm[i];

            if (greenHstgrm[256] < greenHstgrm[i])
                greenHstgrm[256] = greenHstgrm[i];

            if (blueHstgrm[256] < blueHstgrm[i])
                blueHstgrm[256] = blueHstgrm[i];

            // values of colour components
            redHstgrm[257] += i*redHstgrm[i];
            greenHstgrm[257] += i*greenHstgrm[i];
            blueHstgrm[257] += i*blueHstgrm[i];

            // values of dark and light component
            if (i <= 127)
                bwHstgrm[257] += (127-i)*bwHstgrm[i];
            else
                bwHstgrm[258] += (i-127)*bwHstgrm[i];
        }
    }
}



void Histogram::paintEvent(QPaintEvent * event)
{
    Q_UNUSED(event);

    int step = 100;              // distance between histograms
    int height = 255+10;        // histograms height
    int xBase = 99;             // x coordinate of the first histogram origin
    int yBase = 30+height+1;    // y coordinate of the first histogram origin

    QPainter painter(this);
    painter.setPen(Qt::black);


    // 显示在第一行
    // bw hstgrm
    if (bwHstgrm[256] != -1)
        drawBwHstgrm(xBase, yBase, height);
    else
        painter.drawText(xBase, yBase-height/2+5, tr("Can't load the gray levels histogram."));

    // red hstgrm
    if (redHstgrm[256] != -1)
        drawRedHstgrm(xBase+step+height, yBase, height);
    else
        painter.drawText(xBase+step+height+1-height/2+5, yBase, tr("Can't load the red component histogram."));


    // 显示在第二行
    // green hstgrm
    if (greenHstgrm[256] != -1)
        drawGreenHstgrm(xBase, yBase+step+height+1, height);
    else
        painter.drawText(xBase, yBase+(step+height+1), tr("Can't load the green component histogram."));
    // blue hstgrm
    if (blueHstgrm[256] != -1)
        drawBlueHstgrm(xBase+step+height, yBase+step+height+1, height);
    else
        painter.drawText(xBase+step+height, yBase+step+height+1, tr("Can't load the blue component histogram."));

}



void Histogram::drawBwHstgrm(int xBase, int yBase, int height)
{
    QPainter painter(this);

    painter.setPen(Qt::darkGray);

    float max = bwHstgrm[256];

    if (max < redHstgrm[256])
        max = redHstgrm[256];

    if (max < greenHstgrm[256])
        max = greenHstgrm[256];

    if (max < blueHstgrm[256])
        max = blueHstgrm[256];

    // drawing the histogram
    for(int i = 0;i<256;i++)
    {
        painter.drawLine(xBase+1+i, yBase, xBase+1+i,
            yBase-(float)(256./max)*(float)bwHstgrm[i]);
    }

    painter.drawText(xBase, yBase+25, tr("black"));
    painter.drawText(xBase+220, yBase+25, tr("white"));

    painter.setPen(Qt::black);

    painter.drawText(xBase+40, yBase-height-10, tr("GRAY LEVELS HISTOGRAM"));

    painter.drawText(xBase+100, yBase+15, tr("Intensity"));
    painter.drawText(xBase-84, yBase-height/2+5, tr("Pixels count"));

    // abscissa
    painter.drawLine(xBase, yBase, xBase+256+1, yBase);
    painter.drawLine(xBase, yBase+1, xBase+256+1, yBase+1);

    // left ordinate
    painter.drawLine(xBase, yBase, xBase, yBase-height);
    painter.drawLine(xBase-1, yBase, xBase-1, yBase-height);

    // right ordinate
    painter.drawLine(xBase+256+1, yBase, xBase+256+1, yBase-height);
    painter.drawLine(xBase+256+2, yBase, xBase+256+2, yBase-height);

    // left ordinate arrow
    painter.drawLine(xBase, yBase-height, xBase+4, yBase-height+7);
    painter.drawLine(xBase-1, yBase-height, xBase-1-4, yBase-height+7);

    // right ordinate arrow
    painter.drawLine(xBase+256+1, yBase-height, xBase+256+1-4, yBase-height+7);
    painter.drawLine(xBase+256+2, yBase-height, xBase+256+2+4, yBase-height+7);
}



void Histogram::drawRedHstgrm(int xBase, int yBase, int height)
{
    QPainter painter(this);

    painter.setPen(Qt::darkRed);

    float max = bwHstgrm[256];

    if (max < redHstgrm[256])
        max = redHstgrm[256];

    if (max < greenHstgrm[256])
        max = greenHstgrm[256];

    if (max < blueHstgrm[256])
        max = blueHstgrm[256];

    // drawing the histogram
    for(int i = 0;i<256;i++)
    {
        painter.drawLine(xBase+1+i, yBase, xBase+1+i,
            yBase-(float)(256./max)*(float)redHstgrm[i]);
    }

    painter.drawText(xBase, yBase+25, tr("dark"));
    painter.drawText(xBase+225, yBase+25, tr("light"));

    painter.setPen(Qt::black);

    painter.drawText(xBase+25, yBase-height-10, tr("RED COMPONENT HISTOGRAM"));

    painter.drawText(xBase+100, yBase+15, tr("Intensity"));
    painter.drawText(xBase-84, yBase-height/2+5, tr("Pixels count"));

    // abscissa
    painter.drawLine(xBase, yBase, xBase+256+1, yBase);
    painter.drawLine(xBase, yBase+1, xBase+256+1, yBase+1);

    // left ordinate
    painter.drawLine(xBase, yBase, xBase, yBase-height);
    painter.drawLine(xBase-1, yBase, xBase-1, yBase-height);

    // right ordinate
    painter.drawLine(xBase+256+1, yBase, xBase+256+1, yBase-height);
    painter.drawLine(xBase+256+2, yBase, xBase+256+2, yBase-height);

    // left ordinate arrow
    painter.drawLine(xBase, yBase-height, xBase+4, yBase-height+7);
    painter.drawLine(xBase-1, yBase-height, xBase-1-4, yBase-height+7);

    // right ordinate arrow
    painter.drawLine(xBase+256+1, yBase-height, xBase+256+1-4, yBase-height+7);
    painter.drawLine(xBase+256+2, yBase-height, xBase+256+2+4, yBase-height+7);
}



void Histogram::drawGreenHstgrm(int xBase, int yBase, int height)
{
    QPainter painter(this);

    painter.setPen(Qt::darkGreen);

    float max = bwHstgrm[256];

    if (max < redHstgrm[256])
        max = redHstgrm[256];

    if (max < greenHstgrm[256])
        max = greenHstgrm[256];

    if (max < blueHstgrm[256])
        max = blueHstgrm[256];

    // drawing the histogram
    for(int i = 0;i<256;i++)
    {
        painter.drawLine(xBase+1+i, yBase, xBase+1+i,
            yBase-(float)(256./max)*(float)greenHstgrm[i]);
    }

    painter.drawText(xBase, yBase+25, tr("dark"));
    painter.drawText(xBase+225, yBase+25, tr("light"));

    painter.setPen(Qt::black);

    painter.drawText(xBase+15, yBase-height-10, tr("GREEN COMPONENT HISTOGRAM"));

    painter.drawText(xBase+100, yBase+15, tr("Intensity"));
    painter.drawText(xBase-84, yBase-height/2+5, tr("Pixels count"));

    // abscissa
    painter.drawLine(xBase, yBase, xBase+256+1, yBase);
    painter.drawLine(xBase, yBase+1, xBase+256+1, yBase+1);

    // left ordinate
    painter.drawLine(xBase, yBase, xBase, yBase-height);
    painter.drawLine(xBase-1, yBase, xBase-1, yBase-height);

    // right ordinate
    painter.drawLine(xBase+256+1, yBase, xBase+256+1, yBase-height);
    painter.drawLine(xBase+256+2, yBase, xBase+256+2, yBase-height);

    // left ordinate arrow
    painter.drawLine(xBase, yBase-height, xBase+4, yBase-height+7);
    painter.drawLine(xBase-1, yBase-height, xBase-1-4, yBase-height+7);

    // right ordinate arrow
    painter.drawLine(xBase+256+1, yBase-height, xBase+256+1-4, yBase-height+7);
    painter.drawLine(xBase+256+2, yBase-height, xBase+256+2+4, yBase-height+7);
}



void Histogram::drawBlueHstgrm(int xBase, int yBase, int height)
{
    QPainter painter(this);

    painter.setPen(Qt::darkBlue);

    float max = bwHstgrm[256];

    if (max < redHstgrm[256])
        max = redHstgrm[256];

    if (max < greenHstgrm[256])
        max = greenHstgrm[256];

    if (max < blueHstgrm[256])
        max = blueHstgrm[256];

    // drawing the histogram
    for(int i = 0;i<256;i++)
    {
        painter.drawLine(xBase+1+i, yBase, xBase+1+i,
            yBase-(float)(256./max)*(float)blueHstgrm[i]);
    }

    painter.drawText(xBase, yBase+25, tr("dark"));
    painter.drawText(xBase+225, yBase+25, tr("light"));

    painter.setPen(Qt::black);

    painter.drawText(xBase+20, yBase-height-10, tr("BLUE COMPONENT HISTOGRAM"));

    painter.drawText(xBase+100, yBase+15, tr("Intensity"));
    painter.drawText(xBase-84, yBase-height/2+5, tr("Pixels count"));

    // abscissa
    painter.drawLine(xBase, yBase, xBase+256+1, yBase);
    painter.drawLine(xBase, yBase+1, xBase+256+1, yBase+1);

    // left ordinate
    painter.drawLine(xBase, yBase, xBase, yBase-height);
    painter.drawLine(xBase-1, yBase, xBase-1, yBase-height);

    // right ordinate
    painter.drawLine(xBase+256+1, yBase, xBase+256+1, yBase-height);
    painter.drawLine(xBase+256+2, yBase, xBase+256+2, yBase-height);

    // left ordinate arrow
    painter.drawLine(xBase, yBase-height, xBase+4, yBase-height+7);
    painter.drawLine(xBase-1, yBase-height, xBase-1-4, yBase-height+7);

    // right ordinate arrow
    painter.drawLine(xBase+256+1, yBase-height, xBase+256+1-4, yBase-height+7);
    painter.drawLine(xBase+256+2, yBase-height, xBase+256+2+4, yBase-height+7);
}



int Histogram::getBwHstgrm(int index)
{
    if (index >= 0 && index <= 258)
        return bwHstgrm[index];
    else
        return -2;
}



int Histogram::getRedHstgrm(int index)
{
    if (index >= 0 && index <= 257)
        return redHstgrm[index];
    else
        return -2;
}



int Histogram::getGreenHstgrm(int index)
{
    if (index >= 0 && index <= 257)
        return greenHstgrm[index];
    else
        return -2;
}



int Histogram::getBlueHstgrm(int index)
{
    if (index >= 0 && index <= 257)
        return blueHstgrm[index];
    else
        return -2;
}

调用部分

主窗口中建立了一个Histogram菜单项(Action),以下是触发Action后的操作

/******************************************************************************
 *                           绘制图像直方图
 *****************************************************************************/
void MainWindow::on_actionHistogram_triggered()
{

    QDialog * hstgrmDialog = new QDialog(this);
    QScrollArea * scrollArea = new QScrollArea(hstgrmDialog);
    Histogram * hstgrm = new Histogram(scrollArea);
    hstgrm->computeHstgrm(rightImage->imageObject());

    if (hstgrm == NULL)
        return;


    scrollArea->setWidget(hstgrm);

    QHBoxLayout * layout = new QHBoxLayout;
    layout->addWidget(scrollArea);
    hstgrmDialog->setLayout(layout);

    hstgrm->resize(800, 780);
    hstgrmDialog->setFixedWidth(820);
    scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    scrollArea->adjustSize();

    hstgrmDialog->setWindowTitle("Histogram - ImageQt");

    hstgrmDialog->show();
}

效果图

[caption id="attachment_1667" align="aligncenter" width="848"] 直方图[/caption]

基于Qt的图像处理算法和实现——灰度图像、冷暖色调、亮度

由于学校的课程需要,开始学习基于Qt的图像处理,所有的代码已经公开在Github,这篇文章是《Qt 实现的一个图片查看器》的后续更新,由于代码量越来越大,以后的更新不在放出所有的代码,查看完整代码请移步Github。

Github项目地址:https://github.com/jiangjinteng/ImageQt

这篇文章主要内容包括灰度、温度、亮度

概述

Qt提供了四个图像QImage、QPixmap、QBitmap和QPicture,图像处理时比较常用的是QImage和QPixmap。

QPixmap继承了QPaintDevice,因此,你可以使用QPainter直接在上面绘制图形。QPixmap主要用于绘图,但是不方便访问和修改像素。QPixmap是针对屏幕进行特殊优化的,因此,它与实际的底层显示设备息息相关。注意,这里说的显示设备并不是硬件,而是操作系统提供的原生的绘图引擎。所以,在不同的操作系统平台下,QPixmap的显示可能会有所差别。

QImage在IO操作中有很快的速度,并且给出了访问像素的接口,有时我们也需要借助QPainter来完成某些操作,如后面的添加相框。这篇文章中将大量使用QImage。

 

QBitmap只是一个继承于QPixmap的简单类,它可以确保图片深度为1,所以说,QBitmap实际上是只有黑白两色的图像数据。QBitmap提供单色图像,可以用来制作游标(QCursor)或者笔刷(QBrush)。

QPicture是一个绘画设备类,它记录了并可以重演QPainter的命令。你可以使用QPainter的begin()方法,指定在QPicture上绘图,使用end()方法结束绘图,使用QPicture的save()方法將QPainter所使用过的绘图指令存至档案。要重播绘图指令的话,建立一個QPicture,使用load()方法载入绘图指令的档案,然后在指定的绘图裝置上绘制QPicture。

代码结构

在正式开始之前先介绍下我们的代码结构:

所有的图像处理函数在

tools.h

中声明,在

tools.cpp

中实现,

tools.h

中声明了一个新的命名空间 “Tools”。我们在

mainwindow.cpp

中调用这些图像处理的工具。

tools.h

的部分代码

#ifndef TOOLS_H
#define TOOLS_H

#include <QImage>
#include <QDebug>
#include <QPainter>

namespace Tools {
QImage GreyScale(QImage origin);
QImage Warm(int delta, QImage origin);
QImage Cool(int delta, QImage origin);
QImage DrawFrame(QImage origin, QImage &frame);
}

#endif // TOOLS_H

 

灰度图像

标准的灰度图就是每个像素点的三个通道的值一样或者近似,我们采用的策略是每个通道的值都取三个通道的平均值,比如原色是RGB(100,200,300), 那么最终的RGB就是(100+200+300)/3 = 200

严格意义上来说,我们不应该直接计算平均值。从RGB颜色转灰度值有一个著名的心理学公式:

$$Gray = R*0.299 + G*0.587 + B*0.114$$

对它的更多解释请移步我的这篇文章《基于Qt的图像处理算法和实现——线性灰度变

这个处理的实现:

QImage Tools::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;

}

下面是对 GreyScale() 的调用:

void MainWindow::on_actionGrayscale_triggered()
{   
    QImage newImage = Tools::GreyScale(rightImage->imageObject());
    QPixmap tmpPixmap = QPixmap::fromImage(newImage);

    rightImage->updateImage(newImage);
    rightImage->updatePixmap(tmpPixmap);

    repaintRightScene(tmpPixmap);

}

如上,第3行是正式的调用,其它代码是在屏幕上显示结果的相关处理。效果如下:

[caption id="attachment_1651" align="aligncenter" width="297"] 原始图[/caption]

[caption id="attachment_1652" align="aligncenter" width="297"] 灰度图[/caption]

亮度调节

白色用RGB(255,255,255)表示,黑色用RGB(0,0,0)表示,提高图像亮度即提高像素接近白色的程度,我们需要同时增加三个通道的数值,反之就是变暗。

[caption id="attachment_1662" align="aligncenter" width="560"] 原图[/caption]

[caption id="attachment_1661" align="aligncenter" width="560"] delta=30,调整后[/caption]

我们的函数接收一个QImage参数作为要调整亮度的图像,一个int值delta作为调整范围,正数为加亮,负数为变暗。该函数返回调整后的新图像。

/*****************************************************************************
 *                          Adjust image brightness
 * **************************************************************************/
QImage Tools::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;

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

冷/暖色调

[caption id="attachment_1663" align="aligncenter" width="510"] 原图[/caption]

暖色调是比较复古的风格,视觉效果上比较偏黄,数据上,同时增加红色分量和绿色分量可以使图像“偏黄”

[caption id="attachment_1665" align="aligncenter" width="510"] 暖色调[/caption]

/*****************************************************************************
 *                           Adjust color temperature
 * **************************************************************************/
QImage Tools::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();
//            qDebug()<<r<<" "<<g<<""<<b;

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

和暖色调对应,冷色调即偏蓝色。我们可以只增加蓝色分量的值,不改变其他分量。

[caption id="attachment_1664" align="aligncenter" width="510"] 冷色调[/caption]

QImage Tools::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;

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

 

图像处理中的直方图均衡化

图像直方图(Image Histogram)

说均衡化之前先说下什么是图像直方图,图像直方图是用来表示图像中亮度分布的直方图,标绘了图像中每个亮度值的像素数。

图一

大多数码相机会提供直方图功能,可以用来判断曝光程度。

在「计算机视觉」领域,图像直方图主要用来实现图像的二值化。

那直方图怎么看呢,简单来说就一条规则:“左暗右明”,横坐标的左侧为纯黑、较暗的区域,而右侧为较亮、纯白的区域。因此,一张较暗图片的图像直方图的波峰应该出现在左侧和中间(如上图是一张较暗图像的直方图);而整体明亮、只有少量阴影的图像则相反。如果还是不明白的话,可以来看看知乎的这个回答:点我传送门

直方图均衡化(Histogram Equalization)

从数据上来看,直方图均衡化是指将一幅图像的灰度直方图变平,使变换后的图像中每个灰度值的分布概率相似。从图像效果上来看,直方图均衡化可以增加图像的全局对比度,在增强局部的对比度的同时不影响整体。

图二

如图二,是对图一的均衡化结果。

实现算法

对于一个离散的灰度图像 {x},让 ni 表示灰度 i 出现的次数,这样图像中灰度为 i 的像素的出现概率是

$$p_{x}(i)=p(x=i)=\frac{n_{i}}{n}, 0 \leq i < L$$

L 是图像中所有的灰度数(通常为256), n 是图像中所有的像素数,$p_{x}(i)$实际上是像素值为 i 的归一化到 [0,1]的图像直方图。

把对应于 $p_{x} 的累积分布函数,定义为:

$$cdf_{x}(i)=\sum_{j=0}^{i}p_{x}(j)$$

是图像的累计归一化直方图.

我们创建一个形式为 y = T(x) 的变换,对于原始图像中的每个值它就产生一个 y,这样 y 的累计概率函数就可以在所有值范围内进行线性化,转换公式定义为:$cdf_{y}(i) = iK$,K为一个常数。

CDF的性质允许我们做这样的变换;定义为

$$cdf_{y}(y')=cdf_{y}(T(k))=cdf_{x}(k)$$

其中 k 属于区间 [0,L)。注意 T 将不同的等级映射到 {0..1} 域,为了将这些值映射回它们最初的域,需要在结果上应用下面的简单变换:

$$y'=y\cdot (max\{x\}-min\{x\})+min\{x\}$$

上面描述了灰度图像上使用直方图均衡化的方法,但是通过将这种方法分别用于图像RGB颜色值的红色、绿色和蓝色分量,从而也可以对彩色图像进行处理。

对彩色分量rgb分别做均衡化,会产生奇异的点,图像不和谐。一般采用的是用yuv空间进行亮度的均衡。

示例

直方图均衡化通常是对图像灰度值进行归一化的一个非常好的方法:

def histeq(image, nbr_bins=256):
    """对一幅灰度图像进行直方图均衡化"""

    # 计算图像的直方图
    imhist, bins = histogram(image.flatten(), nbr_bins, normed=True)
    figure()
    plot(imhist)
    show()
    cdf = imhist.cumsum()   # cumulative distribution function
    cdf = 255 * cdf / cdf[-1]  # 使用cdf的最后一个元素,目的是归一化

    # 使用累积分布函数的线性插值,计算新的像素值
    im2 = interp(image.flatten(), bins[:-1], cdf)
    return im2.reshape(image.shape), cdf

函数的两个参数中,im是灰度图像,nbr_bins是直方图中使用小区间的数目,最终返回的是直方图均衡化后的图像,以及用来做像素值映射的累积分布函数。

cumsum()函数可以用来计算累计分布函数,其作用效果如下:

In [1]: d = arange(0,10)

In [2]: cumsum(d)
Out[2]: array([ 0,  1,  3,  6, 10, 15, 21, 28, 36, 45])

imhist即灰度图像的直方图,如图三:

图三

变换前后的灰度直方图和使用的变换函数如图四和图五:

图四

左侧为原始图像和直方图,中间图为灰度变换函数,右侧为直方图均衡化后的图像和相应直方图

[caption id="attachment_1590" align="aligncenter" width="1365"]图五 图五[/caption]

openSUSE的KDE环境下调用python-matplotlib绘图

在openSUSE的KDE环境下,默认不能正常调用python-matplotlib进行绘图,现象为不能正常弹出窗口显示需要的图像。

解决方法如下:

我使用QT5后端,首先安装python-matplotlib-qt5

然后在Python的环境下执行:

>> import matplotlib

>> matplotlib.matplotlib_fname()
u'/usr/lib64/python2.7/site-packages/matplotlib/mpl-data/matplotlibrc'

如图显示出了配置文件的位置,修改该配置文件,修改Agg为Qt5Agg

backend : Qt5Agg

至此一切OK,上面所有的Qt5也可以使用Qt4

「图像识别」形态学:对象计数

scipy.ndimage中的morphology模块可以实现形态学操作。我们可以使用scipy.ndimage中的measurements模块来实现二值图像的计数和度量功能。

from scipy.ndimage import measurements, morphology


# 载入图像,然后使用阈值化操作,以保证处理的图像为二值图像
im = array(Image.open('./data/houses.png').convert('L'))
im = 1*(im<128)

labels, nbr_objects = measurements.label(im)
print "Number of objects: ", nbr_objects

载入图像后,通过阈值化方法来确保图像是二值图像。通过和1相乘,脚本将布尔数组转换成二进制表示。label()函数寻找单个的物体,并且按照它们属于哪个对象将整数标签给像素赋值。得到的图像如图中第一排,可见在一些对象之间有一些小连接。进行二进制开(binary open)操作,可以将其移除:

# 形态学开操作更好地分享各个对象
im_open = morphology.binary_opening(im, ones((9,5)), iterations=2)

labels_open, nbr_objects_open = measurements.label(im_open)
print "Number of objects: ", nbr_objects_open

binary_opening()函数的第二个参数指定一个数组结构元素。该数组表示以一个像素为中心时,使用哪些相邻像素。上面脚本中,使用y方向上的9个像素(上4个,本身1个,下4个),在x方向上使用5个像素。参数iterations指定执行该操作的次数。

binary_closing()函数执行相反的操作。

「计算机视觉」使用图像梯度获得简单物体

要求:使用图像梯度,编写一个在图像中获得简单物体(例如,在白色背景中的正方形)轮廓的函数。

图像强度可以用灰度图像I(对于彩色图像,通常对每个颜色通道分别计算导数)的x和y方向的导数

Ix

Iy进行描述。

图像的梯度向量为$\bigtriangledown I = [I_{x},I_{y}]^T$ 。梯度有两个重要的属性,一个是梯度大小:$$\left | \bigtriangledown I \right | = \sqrt{I_{x}^2+I_{y}^2}$$

它描述了图像强度变化的强弱,一是梯度的角度:$$\alpha =arctan2(I_{y},I_{x})$$

它描述了图像中每个像素上强度变化最大的方向。NumPy中的arctan2()函数返回弧度表示的有符号角度,角度的变化区间为 $-\pi...\pi$。

我们可以用离散近似的方法来计算图像的导数。图像导数大多数可以通过卷积简单地实现:$$I_{x}=I*D_{x}和I_{y}=I*D_{y}$$

对于$D_{x}$和$D_{y}$,通常选择Prewitt滤波器:$$D_{x}=\begin{vmatrix}
-1 & 0 & 1\\
-1 & 0 & 1\\
-1 & 0 & 1
\end{vmatrix}和D_{y}=\begin{vmatrix}
-1 & -1 & -1\\
0 & 0 & 0\\
1 & 1 & 1
\end{vmatrix}$$

或者Sobel滤波器:$$D_{x}=\begin{vmatrix}
-1 & 0 & 1\\
-2 & 0 & 2\\
-1 & 0 & 1
\end{vmatrix}和D_{y}=\begin{vmatrix}
-1 & -2 & -1\\
0 & 0 & 0\\
1 & 2 & 1
\end{vmatrix}$$

上面的方法有一些缺陷:滤波器的尺度需要随着图像分辨率的变化而变化。为了在图像噪声方面更稳健,以及在任意尺度上计算导数,我们可以使用高斯导数滤波器:$$I_{x}=I*G_{σx}和I_{y}=I*G_{σy}$$

其中,$I_{x}=I*G_{σx}和I_{y}=I*G_{σy}$表示$G_{σ}$在x和y方向上的导数,$G_{σ}$为标准差为σ的高斯函数。

用来做测试的两个图片:

图片1

图片2

示例代码:

# coding=utf-8
from numpy import *
from PIL import Image
from pylab import *
from matplotlib.font_manager import *
import pca
from scipy.ndimage import filters
from rof import denoise
from numpy import random


def test4():
    im = array(Image.open("./data/001.png").convert('L'))
    figure()
    gray()

    subplot(3, 3, 1)
    imshow(im)
    title("original")
    subplot(3, 3, 4)
    imshow(im)
    title("original")
    subplot(3, 3, 7)
    imshow(im)
    title("original")

    # Sobel层数滤波器
    imx = zeros(im.shape)
    imy = zeros(im.shape)

    filters.sobel(im, 1, imx)
    filters.sobel(im, 0, imy)

    magnitude = sqrt(imx**2+imy**2)

    subplot(3, 3, 2)
    imshow(imx)
    title("Sobel X")
    subplot(3, 3, 5)
    imshow(imy)
    title("Sobel Y")
    subplot(3, 3, 8)
    title("Sobel Magnitude")
    imshow(magnitude)

    # 高斯导数滤波器
    sigma = 5       #标准差
    imx = zeros(im.shape)
    imy = zeros(im.shape)
    filters.gaussian_filter(im, (sigma, sigma), (0, 1), imx)
    filters.gaussian_filter(im, (sigma, sigma), (1, 0), imy)
    magnitude = sqrt(imx ** 2 + imy ** 2)

    subplot(3, 3, 3)
    imshow(imx)
    title("Gaussian X")
    subplot(3, 3, 6)
    imshow(imy)
    title("Gaussian Y")
    subplot(3, 3, 9)
    imshow(magnitude)
    title("Gaussian Magnitude")
    show()

运行结果:

左边的三个图是未处理的灰度图像,实际是显示出了完整的正方形的,因为图像太小导致横线看不到,放大后如下:

第二个图的处理结果如下: