從零開始做一個簡單的數(shù)字圖像處理系統(tǒng)

此文章為數(shù)組圖像處理課程設(shè)計的筆記弛车。項目我放在了我的GitHub上脓豪。這個系統(tǒng)要求用純VC++實現(xiàn)以下功能:

  1. Open BMP file
    打開一個BMP文件取劫,并在窗口中顯示出來匆笤。

  2. Save to new BMP file
    將當(dāng)前視圖保存為一個新的BMP文件(先彈出一個對話框,輸入一個BMP文件名)谱邪。

  3. Display file header
    按如下的格式顯示文件頭信息:


    頭信息格式
  4. Get pixel value
    取某個位置像素的顏色值炮捧,并顯示出來。

  5. Set pixel value
    設(shè)置某個位置像素的顏色值惦银,并顯示出來咆课。

功能(4)和(5)所需的參數(shù)從對話框中獲取。前面5個功能對灰度圖像和彩色圖像都適用扯俱,后面的功能僅要求針對灰度圖像书蚪。

  1. Image interpolation
    圖像縮放:x和y方向的縮放因子、插值算法選擇(最鄰近和雙線性)迅栅,從對話框中獲取殊校。需要將圖像縮放的結(jié)果顯示出來。
  2. Median filtering
    實現(xiàn)3x3的中值濾波读存,并將結(jié)果顯示出來为流。
  3. Gaussian smoothing
    從對話框中獲取高斯函數(shù)的均方差,對圖像做高斯平滑让簿,并將結(jié)果顯示出來敬察。

功能(9) ~ (13)是選做題。

  1. Histogram equalization
    直方圖均衡化尔当,并將結(jié)果顯示出來莲祸。
  2. Sharpening by gradient
    實現(xiàn)基于梯度的圖像銳化,所需參數(shù)從對話框中獲取椭迎,將銳化結(jié)果顯示出來虫给。
  3. Bilateral filtering
    實現(xiàn)雙邊濾波,參數(shù)sigma_d和sigma_R從對話框中獲取侠碧,并將結(jié)果顯示出來抹估。
  4. Add impulse noise
    在圖像中加入脈沖噪聲,噪聲密度和類型從對話框中獲取弄兜,將噪聲圖像顯示出來药蜻。
  5. Canny edge detection
    實現(xiàn)Canny算子邊緣檢測瓷式,并將結(jié)果顯示出來。

由于Qt把HWND和HDC這種平臺相關(guān)的數(shù)據(jù)類型全都封裝好了语泽,沒有提供直接的操作接口贸典,我決定用Qt自己的繪圖設(shè)備比如QImage實現(xiàn)這些功能。雖然用的數(shù)據(jù)類型是封裝好的踱卵,但是圖片讀取廊驼、顯示和處理算法都在像素級別處理。主要是MFC真的苦手啊惋砂。

以下分功能進(jìn)行記錄妒挎。


0 主界面和數(shù)據(jù)結(jié)構(gòu)設(shè)計

本圖像處理系統(tǒng)需要一個用戶友好的可操作界面。我們可以用Qt自帶的設(shè)計功能來實現(xiàn)這個界面西饵。新建一個MainWindow工程酝掩,Qt會自動給你生成mainwindow.hmainwindow.cppmainwindow.ui眷柔。我在新建工程時期虾,為了和以后可能新建的mianwindow窗口區(qū)分,將這個mainwindow類改名成了psmainwindow類驯嘱。我們先打開mainwindow.ui镶苞,進(jìn)行界面的設(shè)計。
設(shè)計過程中鞠评,為了能夠進(jìn)行良好的布局宾尚、且能夠讓所有控件隨窗口大小改變而改變,我們需要用到Qt的布局管理器谢澈,具體參見這篇文章:

我做出的界面是這個樣子的:


主界面

其中煌贴,每個菜單項下拉內(nèi)容如下:


File菜單
ImageProcessing菜單

About菜單沒有下拉項。

界面設(shè)計結(jié)構(gòu)如下:


界面層級結(jié)構(gòu)

接下來需要進(jìn)行數(shù)據(jù)結(jié)構(gòu)的設(shè)計锥忿。為了打開一張BMP圖片牛郑,我們首先要知道BMP圖片是如何被存儲的,之后才能夠?qū)ζ湓O(shè)計數(shù)據(jù)結(jié)構(gòu)敬鬓,讀出圖像放入內(nèi)存淹朋。
為了學(xué)習(xí)BMP圖像的存儲,有兩個資料可供參考钉答。一個是B站上的一個數(shù)字圖像處理課:
數(shù)字圖像處理-Digital Image Processing (DIP)
還有一篇博客:
圖像識別_2010暑期實訓(xùn)有感【二】
BMP圖像文件的數(shù)據(jù)結(jié)構(gòu)如下:

//bmpfile.h
#ifndef BMPFILE_H
#define BMPFILE_H

#include <QtGlobal>
#include <QDebug>

typedef unsigned char BYTE;
typedef unsigned short WORD;//2byte
typedef unsigned int DWORD;//4byte
typedef qint32 LONG;//long 32bit

//位圖文件頭定義;
//其中不包含文件類型信息(由于結(jié)構(gòu)體的內(nèi)存結(jié)構(gòu)決定础芍,
//要是加了的話將不能正確讀取文件信息)
typedef struct  tagBITMAPFILEHEADER{
    WORD bfType;//文件類型,必須是0x424D数尿,即字符“BM”
    DWORD bfSize;//文件大小
    WORD bfReserved1;//保留字
    WORD bfReserved2;//保留字
    DWORD bfOffBits;//從文件頭到實際位圖數(shù)據(jù)的偏移字節(jié)數(shù)
}BITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER{
    DWORD biSize;//信息頭大小
    LONG biWidth;//圖像寬度
    LONG biHeight;//圖像高度
    WORD biPlanes;//位平面數(shù)仑性,必須為1
    WORD biBitCount;//每像素位數(shù):1,4,8,24
    DWORD  biCompression; //壓縮類型
    DWORD  biSizeImage; //壓縮圖像大小字節(jié)數(shù)
    LONG  biXPelsPerMeter; //水平分辨率
    LONG  biYPelsPerMeter; //垂直分辨率
    DWORD  biClrUsed; //位圖實際用到的色彩數(shù)
    DWORD  biClrImportant; //本位圖中重要的色彩數(shù)
}BITMAPINFOHEADER; //位圖信息頭定義

typedef struct tagRGBQUAD{//24位時沒有這個
    BYTE rgbBlue; //該顏色的藍(lán)色分量
    BYTE rgbGreen; //該顏色的綠色分量
    BYTE rgbRed; //該顏色的紅色分量
    BYTE rgbReserved; //保留值
}RGBQUAD;//調(diào)色板定義

//像素信息:24位
typedef struct tagIMAGEDATA
{
    BYTE blue;//當(dāng)1,4右蹦,8時诊杆,用blue存儲信息
    BYTE green;
    BYTE red;
}IMAGEDATA;


#endif // BMPFILE_H

為了方便對這個數(shù)據(jù)結(jié)構(gòu)進(jìn)行操作歼捐,我定義了BMPIMG類,用于存儲整張圖像:

#ifndef BMPIMG_H
#define BMPIMG_H

#include <QtCore/QString>
#include <QFile>
#include <QMessageBox>
#include <QDataStream>
#include <QImage>

#include <bmpfile.h>

class BMPIMG
{
private:
    BITMAPFILEHEADER fileHeader;
    BITMAPINFOHEADER infoHeader;
    RGBQUAD *rgbQuad;
    IMAGEDATA *imgData;
};

#endif // BMPIMG_H


1 Open BMP file

功能說明:打開一個BMP文件晨汹,并在窗口中顯示出來豹储。

為了實現(xiàn)這個功能,首先需要能夠獲得圖片的路徑地址淘这,這里我們需要使用到Qt的文件對話框類剥扣,具體使用說明參見這個文章:

在Action Editor中,右鍵單擊actionOpen_BMP_file動作铝穷,點擊“轉(zhuǎn)到槽”钠怯,即可跳轉(zhuǎn)到槽函數(shù),也就是Open BMP File項被點擊之后會運行的函數(shù)氧骤。
在槽函數(shù)中添加添加獲取圖片地址的邏輯:

void PSMainWindow::on_actionOpen_BMP_file_triggered()
{
    QString path = QFileDialog::getOpenFileName(this, tr("Open Image"), ".", tr("Image Files(*.bmp)"));
    if(path.length() == 0) {
        QMessageBox::information(this, tr("Path"), tr("You didn't select any files."));
    }
}

之后我們需要通過圖片路徑讀取圖片。我將這個邏輯放在了BMPIMG類中吃引,通過構(gòu)造函數(shù)實現(xiàn)讀取一張圖片筹陵、并把圖片信息保存在BMPIMG對象中,然后才能顯示镊尺。

1.1 讀取圖片

定義bool getImage(QString filename)函數(shù)朦佩。
QFile打開路徑文件,并用QDataStream進(jìn)行數(shù)據(jù)讀取庐氮。參考文章:

這部分代碼如下

bool BMPIMG::getImage(QString filename)
{
    qDebug()<<"open file: " + filename;
    //open file
    QFile file(filename);
    if(!file.open(QIODevice::ReadOnly)){//open file failed
        QMessageBox::warning(0, "Waring", "open file " + filename + " failed!", QMessageBox::Yes);
        return false;
    }
    QDataStream dataStream(&file);
}

之后開始讀取文件內(nèi)容语稠。參考文章如下:

首先讀取開頭2個字節(jié)弄砍,即fileHeader.bfType仙畦,判斷是不是“BM”,即16進(jìn)制的424D音婶,從而判斷選中的是否是BMP圖像慨畸。如果是則進(jìn)行后續(xù)操作,不是則彈出警告對話框衣式,并返回false寸士。

bool BMPIMG::getImage(QString filename)
{
    //前面代碼省略
    //read file header
    dataStream>>fileHeader.bfType;

    if(fileHeader.bfType != 0x424D){//bfType != "BM"
        QMessageBox::warning(0, "Waring", "file " + filename + " is not a BMP image!", QMessageBox::Yes);
        return false;
    }
}

之后開始讀取圖片的其余頭數(shù)據(jù),即fileHeaderinfoHeader碴卧。要注意的是弱卡,BMP文件是小端存儲,即低位在前住册,高位在后婶博,比如讀取數(shù)據(jù)3A 00 00 00,實際上是00 00 00 3A荧飞,也就是3A凡蜻;而Qt的QDataStream類則是默認(rèn)的大端讀取搭综,所以我們要先設(shè)置一下大小端,再進(jìn)行數(shù)據(jù)的讀取划栓。參考文章:

bool BMPIMG::getImage(QString filename)
{
    //前略
    dataStream.setByteOrder(QDataStream::LittleEndian);
    dataStream>>fileHeader.bfSize;
    dataStream>>fileHeader.bfReserved1;
    dataStream>>fileHeader.bfReserved2;
    dataStream>>fileHeader.bfOffBits;

    //read info header
    qDebug()<<"reading info header...";
    dataStream>>infoHeader.biSize;
    dataStream>>infoHeader.biWidth;
    dataStream>>infoHeader.biHeight;
    dataStream>>infoHeader.biPlanes;
    dataStream>>infoHeader.biBitCount;
    dataStream>>infoHeader.biCompression; //壓縮類型
    dataStream>>infoHeader.biSizeImage; //壓縮圖像大小字節(jié)數(shù)
    dataStream>>infoHeader.biXPelsPerMeter; //水平分辨率
    dataStream>>infoHeader.biYPelsPerMeter; //垂直分辨率
    dataStream>>infoHeader.biClrUsed; //位圖實際用到的色彩數(shù)
}

這里有一個坑:256色bmp位圖兑巾,有時biClrUsed會被存儲為0。所以我在函數(shù)中添加了一個判斷:如果是8位存儲的圖像且biClrUsed = 0忠荞,則將biClrUsed賦值為256蒋歌。

bool BMPIMG::getImage(QString filename)
{
    //前略
    if(infoHeader.biClrUsed == 0 && infoHeader.biBitCount == 8)
        infoHeader.biClrUsed = 256;
    dataStream>>infoHeader.biClrImportant; //本位圖中重要的色彩數(shù)
}

接下來讀取調(diào)色盤數(shù)據(jù)。需要注意的是委煤,如果圖片是24位存儲的話堂油,則沒有調(diào)色盤數(shù)據(jù),而直接在image data中存儲RGB值碧绞,所以需要做一下判斷府框。

bool BMPIMG::getImage(QString filename)
{
    //前略
    if(infoHeader.biBitCount != 24){
        //read rgbquad
        qDebug()<<"reading rgbquad...";
        rgbQuad = (RGBQUAD*)malloc(sizeof(RGBQUAD) * infoHeader.biClrUsed);
        for(unsigned int nCounti=0; nCounti<infoHeader.biClrUsed; nCounti++){
            dataStream>>(*(rgbQuad + nCounti)).rgbBlue;
            dataStream>>(*(rgbQuad + nCounti)).rgbGreen;
            dataStream>>(*(rgbQuad + nCounti)).rgbRed;
            dataStream>>(*(rgbQuad + nCounti)).rgbReserved;
        }
    }
}

同樣的,在讀取image data的時候也要對存儲深度讥邻、即biBitCount做一下判斷迫靖。如果是24位真彩色圖像,則RGB三個變量都需要用到兴使;如果是8位的話只需要存儲到rgbBlue里就可以系宜。
這里需要注意的是,需要把位數(shù)對齊7⑵恰m锬痢!励幼!bmp位圖的數(shù)據(jù)在存儲時汰寓,會把每一行的數(shù)據(jù)對齊到4的倍數(shù),比如一個寬為7的圖像苹粟,每行會存儲8個IMAGEDATA數(shù)據(jù)踩寇,最后一個全都是0。

bool BMPIMG::getImage(QString filename)
{
    //前略
    //read image data
    imgData = (IMAGEDATA*)malloc(sizeof(IMAGEDATA) * infoHeader.biWidth * infoHeader.biHeight);
    int cnt = 0;
    int align = (4 - (int)infoHeader.biWidth % 4) % 4;
    IMAGEDATA temp;
    switch (infoHeader.biBitCount) {
        case 8:
            for(int i = 0; i < infoHeader.biHeight; i++){
                for(int j = 0; j < infoHeader.biWidth; j++){
                    dataStream>>(*(imgData + cnt)).blue;
                    cnt++;
                }
                if(align!=0){
                    for(int k=0; k<align; k++){
                        dataStream >> temp.blue;
                    }
                }

            }
        break;
        case 24:
            for(int i = 0; i < infoHeader.biHeight; i++){
                for(int j = 0; j < infoHeader.biWidth; j++){
                    dataStream>>(*(imgData + cnt)).blue;
                    dataStream>>(*(imgData + cnt)).green;
                    dataStream>>(*(imgData + cnt)).red;
                    cnt++;
                }
                if(align!=0){
                    for(int k=0; k<align; k++){
                        dataStream >> temp.blue;
                        dataStream >> temp.green;
                        dataStream >> temp.red;
                    }
                }
            }
        break;
    }
    file.close();
    return true;
}

至此文件讀取完畢六水。

1.2 顯示圖片

在顯示圖片之前首先了解一下坐標(biāo)系的問題俺孙。根據(jù)上面提到的文章圖像識別_2010暑期實訓(xùn)有感【二】

一般來說,.bmp文件的數(shù)據(jù)從下到上掷贾,從左到右的睛榄。也就是說,從文件中最先讀到的是圖象最下面一行的左邊第一個象素想帅,然后是左邊第二個象素……接下來是倒數(shù)第二行左邊第一個象素场靴,左邊第二個象素……依次類推 ,最后得到的是最上面一行的最右一個象素。

QImage的坐標(biāo)系是反著的旨剥,左上角才是原點咧欣,所以在輸出(setPixel)的時候,需要從大數(shù)開始輸出轨帜∑枪荆基本流程就是,按順序讀取每一個像素的rgb值蚌父, 然后設(shè)置這個位置的像素為這個顏色哮兰。

要顯示圖片,要先將BMPIMG對象里存儲的IMAGEDATA信息輸出為QImage苟弛。由于我不希望調(diào)用QImage自帶的打開圖片接口喝滞,而想在像素級別進(jìn)行操作,所以我需要QImage像素級操作的接口膏秫。參考:

我們需要用到void QImage::setPixelColor(int x, int y, const [QColor](qcolor.html) &color)這個API右遭。

BMPIMG類中添加QImage toQImage()函數(shù):

QImage BMPIMG::toQImage()
{
    int cnt = 0;
    QImage outputImg = QImage(infoHeader.biWidth, infoHeader.biHeight, QImage::Format_ARGB32);
    QPoint pos;
    QColor color;
    BYTE  rgb;
    if(infoHeader.biBitCount == 24){
        for(int i=infoHeader.biHeight-1; i>=0; i--){
            for(int j=0; j<infoHeader.biWidth; j++){
                pos = QPoint(j,i);
                color = QColor((imgData + cnt)->red, (imgData + cnt)->green, (imgData + cnt)->blue);
                outputImg.setPixelColor(pos, color);
                cnt++;
            }
        }
    }
    else{
        for(int i=infoHeader.biHeight-1; i>=0; i--){
            for(int j=0; j<infoHeader.biWidth; j++){
                pos = QPoint(j,i);
                rgb = (imgData + cnt)->blue;
                color = QColor((rgbQuad + rgb)->rgbRed, (rgbQuad + rgb)->rgbGreen, (rgbQuad + rgb)->rgbBlue);
                outputImg.setPixelColor(pos, color);
                cnt++;
            }
        }
    }

    return outputImg;
}

這樣一來,要顯示圖片所需的功能就都有了缤削。接下來我們在Open BMP file項的槽函數(shù)中繼續(xù)添加讀取文件窘哈、顯示圖像的邏輯,也就是調(diào)用我們剛寫好的BMPIMG類的函數(shù)僻他。

void PSMainWindow::on_actionOpen_BMP_file_triggered()
{
    //前略
    BMPIMG image(path);
    setImg(image);
    qDebug()<<"here";
    QImage qImage = image.toQImage();
    QGraphicsScene *scene = new QGraphicsScene();
    scene->addPixmap(QPixmap::fromImage(qImage));
    qDebug()<<qImage;
    ui->graphicsView->setScene(scene);
    ui->graphicsView->show();
    qDebug()<<"here";
    return;
}

這樣一來宵距,點擊Open BMP file項之后就可以看到圖片了腊尚。


Open BMP file

2 Save to new BMP file

功能要求:將當(dāng)前視圖保存為一個新的BMP文件(先彈出一個對話框吨拗,輸入一個BMP文件名)。

和打開文件一樣婿斥,為了獲取保存的路徑劝篷,我們依然需要用到文件對話框,只不過用法和之前不太一樣民宿。參考 Qt入門-打開和保存文件對話框 娇妓。除此之外,在保存之前活鹰,我們需要判斷是否有已經(jīng)打開的圖片哈恰,不然對空對象進(jìn)行操作會報錯。對于判斷是否打開了圖片志群,我是通過fileHeader.bfType這一項來判斷的着绷。如果打開的文件是bmp文件,那么這個變量的值應(yīng)該是0x424D锌云,我修改了BMPIMG類的構(gòu)造函數(shù)荠医,使得一個BMPIMG對象剛被構(gòu)造出來時,fileHeader.bfType會被賦值為0:

BMPIMG::BMPIMG()
{
    fileHeader.bfType = 0;
}

同時添加BMPIMG判空函數(shù)bool BMPIMG::isEmpty(),若對象為空則返回true彬向,否則返回false

bool BMPIMG::isEmpty()
{
    if(fileHeader.bfType == 0x424D){
        return false;
    }
    else{
        return true;
    }
}

接下來編寫保存圖像的邏輯兼贡。保存圖像實際上就是打開圖像的逆操作,怎么讀出來的就怎么寫回去娃胆,同樣要注意對齊的問題遍希,用0將每行不足4的倍數(shù)的位置補齊。這里還有一點要注意的是缕棵,對齊時是以BYTE為單位孵班,而非以IMAGEDATA為單位。當(dāng)補齊24位圖像時招驴,不需要補幾個IMAGEDATA篙程,只需要補幾個BYTE

bool BMPIMG::saveImage(QString path){
    QFile newImg(path);
    if(!newImg.open(QIODevice::WriteOnly)){
        QMessageBox::warning(0, "Waring", "save file " + path + " failed!", QMessageBox::Yes);
        return false;
    }
    QDataStream dataStream(&newImg);
    dataStream<<fileHeader.bfType;
    dataStream.setByteOrder(QDataStream::LittleEndian);
    dataStream<<fileHeader.bfSize;
    dataStream<<fileHeader.bfReserved1;
    dataStream<<fileHeader.bfReserved2;
    dataStream<<fileHeader.bfOffBits;

    //write info header
    dataStream<<infoHeader.biSize;
    dataStream<<infoHeader.biWidth;
    dataStream<<infoHeader.biHeight;
    dataStream<<infoHeader.biPlanes;
    dataStream<<infoHeader.biBitCount;
    dataStream<<infoHeader.biCompression; //壓縮類型
    dataStream<<infoHeader.biSizeImage; //壓縮圖像大小字節(jié)數(shù)
    dataStream<<infoHeader.biXPelsPerMeter; //水平分辨率
    dataStream<<infoHeader.biYPelsPerMeter; //垂直分辨率
    dataStream<<infoHeader.biClrUsed; //位圖實際用到的色彩數(shù)
    dataStream<<infoHeader.biClrImportant; //本位圖中重要的色彩數(shù)

    if(infoHeader.biBitCount != 24){
        //write rgbquad
        for(unsigned int nCounti=0; nCounti<infoHeader.biClrUsed; nCounti++){
            dataStream<<(*(rgbQuad + nCounti)).rgbBlue;
            dataStream<<(*(rgbQuad + nCounti)).rgbGreen;
            dataStream<<(*(rgbQuad + nCounti)).rgbRed;
            dataStream<<(*(rgbQuad + nCounti)).rgbReserved;
        }
    }

    //write image data
    int cnt = 0;
    int alignByte = (4 - (int)infoHeader.biWidth % 4) % 4;
    IMAGEDATA align;
    align.blue = 0;
    align.green = 0;
    align.red = 0;
    switch (infoHeader.biBitCount) {
        case 8:
            for(int i = 0; i < infoHeader.biHeight; i++){
                for(int j = 0; j < infoHeader.biWidth; j++){
                    dataStream<<(imgData+cnt)->blue;
                    cnt++;
                }
                if(alignByte != 0){
                    for(int k = 0; k < alignByte; k++){
                        dataStream<<align.blue;
                    }
                }
            }
        break;
        case 24:
            for(int i = 0; i < infoHeader.biHeight; i++){
                for(int j = 0; j < infoHeader.biWidth; j++){
                    dataStream<<(imgData+cnt)->blue;
                    dataStream<<(imgData+cnt)->green;
                    dataStream<<(imgData+cnt)->red;
                    cnt++;
                }
                if(alignByte != 0){
                    for(int k = 0; k < alignByte; k++){
                        dataStream<<align.blue;
                    }
                }
            }
        break;
    }
    newImg.close();
    return true;
}

現(xiàn)在我們有了所有的模塊别厘,可以將他們拼在一起了虱饿。右鍵添加Save to new BMP file的槽函數(shù),先圖片判空触趴,再地址判空氮发,最后寫圖片:

void PSMainWindow::on_actionSave_to_new_BMP_file_triggered()
{
    if(image.isEmpty()){
        QMessageBox::information(this, tr("warning"), tr("Please open an image first."));
        return;
    }
    QString path = QFileDialog::getSaveFileName(this, tr("Save Image"), " ", tr("Image Files(*.bmp)"));
    if(!path.isNull()){
        image.saveImage(path);
    }
    else{
        QMessageBox::information(this, tr("Path"), tr("You didn't input a file name."));
    }
}

保存功能完成。


3 Display file header

首先設(shè)計顯示文件頭信息的窗口headerInfoDialog冗懦。在普通的Dialog中加入一個List Widget控件和一個按鈕爽冕,用于顯示文件頭信息。

headerInfoDialog類中添加顯示信息的接口:
void HeaderInfoDialog::setInfo(BITMAPFILEHEADER fileHeader, BITMAPINFOHEADER infoHeader)
按要求的格式披蕉,將說明和值拼接成字符串颈畸,調(diào)用List Widget控件的setItem函數(shù)即可添加信息。每次展示信息時没讲,先將控件內(nèi)容清空眯娱,再依次添加信息。有一些需要用到數(shù)學(xué)計算的信息爬凑,計算函數(shù)可參考Qt下常用的數(shù)值計算(絕對值qAbs徙缴,最大qMax,最小qMin嘁信,開根號Sqrt于样,N次方是pow,斷言宏Q_ASSERT和Q_ASSERT_X )潘靖。

void HeaderInfoDialog::setInfo(BITMAPFILEHEADER fileHeader, BITMAPINFOHEADER infoHeader)
{
    ui->listWidget->clear();
    QString line;
    line = "btType (file type) = " + QString::number(fileHeader.bfType);
    ui->listWidget->addItem(line);
    line = "bfSize (file length) = " + QString::number(fileHeader.bfSize);
    ui->listWidget->addItem(line);
    line = "bfOffBits (offset of bit map data in bytes) = " + QString::number(fileHeader.bfOffBits);
    ui->listWidget->addItem(line);
    line = "biSize (header structure length shoud be 40 or 0x28) = " + QString::number(infoHeader.biSize);
    ui->listWidget->addItem(line);
    line = "biWidth (image width) = " + QString::number(infoHeader.biWidth);
    ui->listWidget->addItem(line);
    line = "biHeight (image height) = " + QString::number(infoHeader.biHeight);
    ui->listWidget->addItem(line);
    line = "biPlanes (must be eaual to 1) = " + QString::number(infoHeader.biPlanes);
    ui->listWidget->addItem(line);
    line = "biBitCount (color/pixel bits) = " + QString::number(infoHeader.biBitCount);
    ui->listWidget->addItem(line);
    line = "biCompression (compressed?) = " + QString::number(infoHeader.biCompression);
    ui->listWidget->addItem(line);
    line = "biSizeImage (length of bit map data in bytes must be the times of 4) = " + QString::number(infoHeader.biSizeImage);
    ui->listWidget->addItem(line);
    line = "biXPelsPerMeter (horizontal resolution of target device in pixels/metre) = " + QString::number(infoHeader.biXPelsPerMeter);
    ui->listWidget->addItem(line);
    line = "biYpelsPerMeter (vertical resolution of target device in pixels/metre) = " + QString::number(infoHeader.biYPelsPerMeter);
    ui->listWidget->addItem(line);
    line = "biColorUsed (number of colors used in bitmap, 0 = 2**biBitCount) = " + QString::number(infoHeader.biClrUsed);
    ui->listWidget->addItem(line);
    line = "biColorImportant (number of important colors, 0 = all colors are impretant) = " + QString::number(infoHeader.biClrImportant);
    ui->listWidget->addItem(line);

    line = "";
    ui->listWidget->addItem(line);

    line = "The following is additional information:";
    ui->listWidget->addItem(line);
    line = "Bytes per row in bitmap (nBytesPerRow) = " + QString::number(infoHeader.biWidth * (infoHeader.biBitCount/8));
    ui->listWidget->addItem(line);
    line = "Total bytes of bitmap (nImageSizeInByte) = " + QString::number(fileHeader.bfSize - fileHeader.bfOffBits);
    ui->listWidget->addItem(line);
    line = "Actual pixels per row in bitmap (nPixelsPerRpe)= " + QString::number(infoHeader.biWidth);
    ui->listWidget->addItem(line);
    line = "Total rows of bitmap (nTotalRows) = " + QString::number(infoHeader.biHeight);
    ui->listWidget->addItem(line);
    line = "Total colors (2**biBitCount)(nTotalColors) = " + QString::number(pow(2, infoHeader.biBitCount));
    ui->listWidget->addItem(line);
    line = "Used colors (biColorUsed)(nUsedolors) = " + QString::number(infoHeader.biClrUsed);
    ui->listWidget->addItem(line);
}

為了將信息傳到頭信息展示窗口中捐康,我們還需要獲取圖片的fileHeaderinfoHeader這兩個私有成員忠聚。在BMPIMG類中添加相應(yīng)接口:

BITMAPFILEHEADER BMPIMG::getFileHeader()
{
    return fileHeader;
}

BITMAPINFOHEADER BMPIMG::getInfoHeader()
{
    return infoHeader;
}

接下來編寫Display file header槽函數(shù)中的邏輯。先定義一個新的信息展示窗口,獲取文件頭信息和信息頭信息蚀乔,設(shè)置展示內(nèi)容,最后調(diào)用show()函數(shù)運行窗口即可。
這里需要注意的是,如果直接定義窗口涮坐、調(diào)用show()函數(shù),窗口運行之后會馬上自動退出誓军,解決方法見:關(guān)于窗口閃退的解決袱讹。我采取了樓主所說的第一種方法:

將dlg作為類的成員,而不是在函數(shù)內(nèi)部昵时。

在psmainwindow.h中添加展示窗口的定義:

//psmainwindow.h
#ifndef PSMAINWINDOW_H
#define PSMAINWINDOW_H

//頭文件略

namespace Ui {
class PSMainWindow;
}

class PSMainWindow : public QMainWindow
{
    Q_OBJECT

//前略
public:
    explicit PSMainWindow(QWidget *parent = nullptr);
    ~PSMainWindow();

private:
    HeaderInfoDialog headerInfoDialog;
};

#endif // PSMAINWINDOW_H

右鍵添加槽函數(shù):

void PSMainWindow::on_actionDisplay_file_header_triggered()
{
    if(image.isEmpty()){
        QMessageBox::information(this, tr("warning"), tr("You didn't open any image, please open an image first."));
        return;
    }
    BITMAPFILEHEADER fileHeader = image.getFileHeader();
    BITMAPINFOHEADER infoHeader = image.getInfoHeader();
    headerInfoDialog.setInfo(fileHeader, infoHeader);
    headerInfoDialog.show();
}

4 Get pixel value

功能要求:取某個位置像素的顏色值捷雕,并顯示出來。

先實現(xiàn)獲取顏色值的邏輯壹甥。
首先要實現(xiàn)定位讀取IMAGEDATA救巷。在BMPIMG類中添加:

IMAGEDATA BMPIMG::getPixelData(int x, int y)
{
    IMAGEDATA pix = *(imgData + y * infoHeader.biWidth + x);
    return pix;
}

直接返回特定位置的IMAGEDATA信息。接下來判斷biBitCount句柠,分情況解析顏色信息:

QColor BMPIMG::getPixel(int x, int y)
{
    IMAGEDATA pix = getPixelData(x, y);
    QColor color;
    switch(infoHeader.biBitCount){
        case 8:
            color.setRed((rgbQuad + pix.blue)->rgbRed);
            color.setBlue((rgbQuad + pix.blue)->rgbBlue);
            color.setGreen((rgbQuad + pix.blue)->rgbGreen);
        break;
        case 24:
            color.setRed(pix.red);
            color.setBlue(pix.blue);
            color.setGreen(pix.green);
        break;
    }
    return color;
}

設(shè)計獲取坐標(biāo)的position窗口浦译。新建一個Dialog,在對話框中添加兩組標(biāo)簽和Spin Box(整型數(shù)輸入框)溯职,和一個OK按鈕精盅。

position窗口

此處我們需要將子窗口position中獲取的值傳回父窗口psmainwindow,傳值方法可以參考Qt窗體之間相互傳值的三種方式谜酒。這里我采用了信號和槽的方法叹俏。
編輯position類的頭文件position.h,定義信號函數(shù):

#ifndef POSITION_H
#define POSITION_H

#include <QDialog>

namespace Ui {
class position;
}

class position : public QDialog
{
    Q_OBJECT

//前略
signals:
    void send_position(QPoint p);
};

#endif // POSITION_H

定義好之信號后只需emit send_position(p);即可發(fā)射信號僻族。
編輯psmainwindow類粘驰,定義對應(yīng)的槽函數(shù)用于接收信號,并定義QPoint p成員用于存儲接收到的坐標(biāo):

#ifndef PSMAINWINDOW_H
#define PSMAINWINDOW_H

//頭文件略

namespace Ui {
class PSMainWindow;
}

class PSMainWindow : public QMainWindow
{
    Q_OBJECT

//前略
private slots:
    void receivePosition(QPoint p);

private:
    QPoint p;
};

#endif // PSMAINWINDOW_H

position的設(shè)計界面右鍵點擊OK按鈕添加槽函數(shù)鹰贵,獲取坐標(biāo)并發(fā)射信號:

void position::on_pushButton_clicked()
{
    int x = ui->xInput->value();
    int y = ui->yInput->value();
    QPoint p(x,y);
    emit send_position(p);
    this->close();
}

編輯psmainwindow.cpp晴氨,完成槽函數(shù)void receivePosition(QPoint p)

void PSMainWindow::receivePosition(QPoint p)
{
    this->p = p;
}

接下來設(shè)計展示顏色的窗口colorDisplay康嘉。用Label來展示顏色值碉输,再添加一個小塊的GraphicsView來直觀地顯示顏色:

colorDisplay

定義接口void colorDisplay::setInfo(QColor color)來設(shè)置展示的顏色:

void colorDisplay::setInfo(QColor color){
    ui->Red->setText(QString::number(color.red()));
    ui->Green->setText(QString::number(color.green()));
    ui->Blue->setText(QString::number(color.blue()));
    QImage *colorBlock = new QImage(40, 40, QImage::Format_A2BGR30_Premultiplied);
    colorBlock->fill(color);
    QGraphicsScene *scene = new QGraphicsScene();
    scene->addPixmap(QPixmap::fromImage(*colorBlock));
    ui->graphicsView->setScene(scene);
}

之后我們就可以寫Get pixel value本體了。在設(shè)計界面添加Get pixel value的槽函數(shù):

void PSMainWindow::on_actionGet_pixel_value_triggered()
{
    if(image.isEmpty()){
        QMessageBox::information(this, tr("warning"), tr("Please open an image first."));
        return;
    }
    position *dlg = new position(this);
    connect(dlg, SIGNAL(send_position(QPoint)), this, SLOT(receivePosition(QPoint)));
    dlg->setMax(image.getInfoHeader().biWidth, image.getInfoHeader().biHeight);
    dlg->exec();
    QColor pix = image.getPixel(p.x(), p.y());
    colorDisplay *displayDialog = new colorDisplay();
    displayDialog->setInfo(pix);
    displayDialog->show();
}

這里有一點需要注意的是亭珍,Dialog::shwo()函數(shù)是展示子窗口之后敷钾,父窗口還會繼續(xù)運行;而Dialog::exec()函數(shù)是等待子窗口運行結(jié)束(即關(guān)閉)之后肄梨,父窗口才會繼續(xù)運行阻荒。我們需要獲取到坐標(biāo)之后才能讀取像素值,所以獲取坐標(biāo)的窗口position *dlg要用exec()運行众羡,而父窗口不依賴展示窗口colorDisplay *displayDialog侨赡,直接調(diào)用show()就可以。


5 Set pixel value

功能要求:設(shè)置某個位置像素的顏色值,并顯示出來羊壹。

BMPIMG類中添加void setPixel(int r, int b, int g, int x, int y)函數(shù):

void BMPIMG::setPixel(int r, int b, int g, int x, int y)
{
    if(infoHeader.biBitCount == 8){
        BYTE color = getColor(r,b,g);
        (imgData + y * infoHeader.biWidth + x)->blue = color;
    }
    else{
        (imgData + y * infoHeader.biWidth + x)->blue = b;
        (imgData + y * infoHeader.biWidth + x)->green = g;
        (imgData + y * infoHeader.biWidth + x)->red = r;
    }
    return;
}

這里有一個敗筆:我之前寫的getPixelData(int x, int y)函數(shù)返回值不是指針蓖宦,所以當(dāng)需要修改像素顏色值時,這個函數(shù)無法被復(fù)用油猫。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市情妖,隨后出現(xiàn)的幾起案子睬关,更是在濱河造成了極大的恐慌,老刑警劉巖毡证,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件电爹,死亡現(xiàn)場離奇詭異,居然都是意外死亡料睛,警方通過查閱死者的電腦和手機藐不,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秦效,“玉大人雏蛮,你說我怎么就攤上這事≮逯荩” “怎么了挑秉?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長苔货。 經(jīng)常有香客問我犀概,道長,這世上最難降的妖魔是什么夜惭? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任姻灶,我火速辦了婚禮,結(jié)果婚禮上诈茧,老公的妹妹穿的比我還像新娘产喉。我一直安慰自己,他們只是感情好敢会,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布曾沈。 她就那樣靜靜地躺著,像睡著了一般鸥昏。 火紅的嫁衣襯著肌膚如雪塞俱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天吏垮,我揣著相機與錄音障涯,去河邊找鬼罐旗。 笑死,一個胖子當(dāng)著我的面吹牛唯蝶,可吹牛的內(nèi)容都是我干的尤莺。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼生棍,長吁一口氣:“原來是場噩夢啊……” “哼颤霎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起涂滴,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤友酱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后柔纵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缔杉,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年搁料,在試婚紗的時候發(fā)現(xiàn)自己被綠了或详。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡郭计,死狀恐怖霸琴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情昭伸,我是刑警寧澤梧乘,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站庐杨,受9級特大地震影響选调,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜灵份,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一仁堪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧填渠,春花似錦弦聂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽剔桨。三九已至屉更,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間洒缀,已是汗流浹背瑰谜。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工欺冀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人萨脑。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓隐轩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親渤早。 傳聞我的和親對象是個殘疾皇子职车,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容

  • ¥開啟¥ 【iAPP實現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 6,444評論 0 17
  • 教程一:視頻截圖(Tutorial 01: Making Screencaps) 首先我們需要了解視頻文件的一些基...
    90后的思維閱讀 4,710評論 0 3
  • 1 實驗?zāi)康?目前計算機視覺技術(shù)已經(jīng)比較成熟鹊杖,相關(guān)的開源項目與算法很多悴灵,可以將這些開源算法進(jìn)行整合,進(jìn)而做成一個小...
    YOUNG_FAN閱讀 6,741評論 0 50
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,111評論 1 32
  • 我要到一個 春天有雨 冬天有雪 秋天有紅葉的地方 在那里 讀書 寫字 和戀愛 說自己想說的話 做自己想做的事 ...
    Alice王志榮閱讀 217評論 0 0