頂帽變換(top-hat)

之前介紹的型態學處理,都是用在二極化後的圖,事實上型態學也能用在灰階圖,與二極化的圖手法相同,只是輸入圖改成灰階圖。

假設有一個球形的結構元素s,開運算相當於推動球沿著曲面的下側面滾動,從左至右直到完成整個圖面,而閉運算相當於讓球體在上表面滾動,下圖分別為灰階圖的開運算和閉運算結果,實務上我們依據影像,選擇適合大小的結構元素s。這邊介紹灰階形態學運用之一的頂帽變換(top-hat),為影像與影像的開運算之差,此為非均勻光照問題的解決方法之一。

下圖為一米粒圖案,高灰度的米粒分散於整體的暗背景中,我們可以發現影像中間部分的背景,比下邊部分的背景還要亮,這是由於不均勻的光照產生的,我們希望把米粒與背景分離開來,進而確認米粒的個數或位置。然而由於整體光線不均勻的情況,下圖為使用OpenCV的自適應濾波adaptiveThreshold ()的結果,無法得到非常滿意的效果。 由於影像的米粒大小差異不大,這時可以選擇比米粒短軸稍大的結構元素s,對影像進行開運算,弭平米粒的強度,很像是早早所說的拖著圓盤,從曲面的下側走過,或像下圖那般消掉區域高點,這時根據影像的位置,可得到該位置的基底強度。 這時我們將原始圖各像素減去此位置的基底強度,我們可以得到亮度比較均勻的米粒圖,這些米粒基本上處於同一高度,此時進行二極化,可以得到比較好的效果。

QTextStream

文本文件是一種人可讀的文件,可以使用QTextStream這方便的操作介面,QTextStream和QDataStream的使用方式類似,只不過它是操作純文本文件的,可以使用 << 與 >> 運算子進行資料的讀取與寫入,像XML、HTML這類格式也可由QTextStream生成,但Qt提供了更方便的XML操作類。

QTextStream::readLine()讀取一行內容,使用QTextStream::readAll()讀取所有內容,或用>>運算子讀取內容(以空白為區隔),這些方法皆會得到QString。

QIODevice有定義不同的開啟或讀取方式:

  • QIODevice::ReadOnly:以唯讀方式打開文件。
  • QIODevice::WriteOnly:以唯寫方式打開文件。
  • QIODevice::ReadWrite:打開的文件可供讀寫。
  • QIODevice::Append:以追加的方式打開,新增內容將從舊文件內容末尾開始。
  • QIODevice::Truncate:以重寫的方式打開,會將舊內容清掉後才新增內容。
  • QIODevice::Text:在讀取時,將行結束符轉換成\n;在寫入時,將行結束符轉換成本地格式,例如Win32 平台上是\r\n。

默認情況下,QTextStream的編碼格式是Unicode,可以使用setCodec(“UTF-8″),變更成UTF-8的格式。


#include <QCoreApplication>
#include <QFile>
#include <QtDebug>
#include <QTextSTream>

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);
    QFile file("data.txt");
    file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate); //沒加QIODevice::Text時,endl無法跳至下一行
    QTextStream out(&file);
    out << QObject::tr("姓名:\tMichael") << endl;
    out << QObject::tr("性別:\t男") << endl;
    out << QObject::tr("年齡:\t28") << endl;
    file.close();

    QFile file2("data.txt");
    file2.open(QIODevice::ReadOnly);
    QTextStream in(&file2);
    in.setCodec("UTF-8");
    while(!file2.atEnd()) {
        const char*tmpChar = file2.readLine();
        QString tmpString = QString::fromLocal8Bit(tmpChar); //加上fromLocal8Bit()才能顯示中文
        qDebug() << tmpString;
    }
    file2.close();
    return app.exec();
}

QTextStream

程式執行

點擊檔案->新增專案或專案,有四種專案類型,我們這邊選擇Widgets應用程式。

enter image description here

enter image description here


對此專案設定名稱和檔案位置。

enter image description here


選擇開發版本

enter image description here


選擇基礎類別和專案窗口的類別名,並完成專案設定。

enter image description here


按下執行後,我們可以看到Qt Creator為我們預設的QWidget。

enter image description here


專案建置完成後,到剛剛設定的路徑,可以看到Qt Creator自動生成的幾個文件。

當編譯完後,Qt Creator會將我們編譯後的檔案另外放置,以我們test的專案為例,同路徑下有build-test-Desktop_Qt_5_4_1_MinGW_32bit-Debug的資料夾,裡面有debug和release版本的exe執行檔。

實際打開debug資料夾內的exe檔,我們會發現錯誤,原因為我們缺少相關的dll檔,這邊有以下兩個解決方法:

  • 假設exe檔不用發佈,單純個人使用,可以在系統的環境變量添加路徑,以win8.1為例,點擊控制台->系統及安全性->系統->進階系統設定->環境變數->編輯,在最後面添加C:\Qt\Qt5.4.1\5.4\mingw491_32\bin後按確定,實際添加內容要看個人的Qt版本及安裝位置。
  • 假設要發佈給別人使用,自然無法要求別人安裝Qt並自行添加路徑,我們以最陽春的mainWidget為例,去Qt安裝目錄下的bin資料夾,這邊的位置在C:\Qt\Qt5.4.1\5.4\mingw491_32\bin內,複製缺少的dll檔放置於exe檔旁,持續運行並添加其他缺少的dll檔,這邊一共複製了以下9個dll檔才啟動成功。

enter image description here

enter image description here


假如我們要發布該程式給別人,由於debug版本的dll檔所佔空間較大,所以通常編譯release版本,發布release版本以及相關dll檔給使用者,這邊我們改變設定,改成編譯release版本。

enter image description here

實際打開release資料夾內的exe檔,也會有同樣錯誤,同樣去Qt安裝目錄下的bin資料夾,複製缺少的dll檔(通常檔名最後會少個d)放置於exe檔旁,也是複製了9個dll檔才運行成功。


所謂的靜態編譯與動態編譯,這兩者是相對的,差別在於發佈時有沒有另外包含dll檔,有包含的為動態編譯,否則為靜態編譯,靜態編譯不需要包含dll檔,代價就是exe檔案內容較大,而且缺乏靈活性無法部屬插件。通常使用較大的函式庫會另外包含dll檔(像Qt、OpenCV等)。

QVariant

QVariant類別類似c++的聯合(union)數據類型,能夠保存許多Qt類別的值,包括int、QString、QColor、QPen、QRect、QSize等,也能夠存Qt的容器。

這邊用QVariant保留各類型的值,要使用時再轉型成原本儲存的類型。


#include <QCoreApplication>
#include<QDebug>
#include <QVariant>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QVariant int_v(20);
    qDebug()<<int_v.toInt();

    QVariant double_v(20.5);
    qDebug()<<double_v.toDouble();

    QVariant string_v("Hi");
    qDebug()<<string_v.toString();

    QStringList sl;
    sl<<"a"<<"b";
    QVariant sl_v(sl);
    if(sl_v.type()==QVariant::StringList){      //判斷QVariant的類型是否為StringList
        QStringList list=sl_v.toStringList();
        for(int i=0;i<list.size();++i)
            qDebug()<<list.at(i);
    }
    return 0;
} 

QVariant

QFile

QFile類別提供了文件讀取和寫入的能力,可以讀寫文本文件、二進位文件和Qt的資源文件,處理文本文件可以使用QTextStream,處理二進制文件可以使用QDataStream,處理文件資訊可以使用QFileInfo,處理目錄使用QDir。

我們通常會將文件路徑作為參數傳給QFile的構造函數,不過也可以在創建好對象後,使用setFileName()函式來修改檔名,QFile在開啟檔案的時候,可以設定開啟模式,例如QIODevice::ReadOnly、QIODevice:: WriteOnly、QIODevice::Append或QIODevice::ReadWrite等,這邊示範如何用QFile進行資料的寫入和讀取。

#include <QCoreApplication>
#include <QFile>
#include <QtDebug>

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    QFile file("test.txt");   //寫資料到test.txt
    file.open(QIODevice::WriteOnly);
    const char* data = "welcome QT!";
    file.write(data);
    file.close();

    QFile file2("test.txt");  //從test.txt讀資料
    file2.open(QIODevice::ReadOnly);
    while(!file2.atEnd()) {
        qDebug() << file2.readLine();
    }
    file2.close();
    return app.exec();
}

QFile

字串(QString)

Qt提供QString類別作為字串操作,QString保存16位元Unicode,提供豐富的操作、查詢和轉換等函數,並進行了隱式共享的優化。


QString提供了+跟+=運算符作為字符相加,我們可用length()檢查字串長度。

String str = "Hello";
str += " Qt";      //str="Hello Qt"
int strLength = str.length();    //strLength=8

QString::append()函式,提供與+=運算符相同功能。

QString str = "Hello";
str. append(" Qt");    //str="Hello Qt"

QString有類似C++的sprintf()函式,可用於格式化字串。

QString str;
str.sprintf("%s:%d", "Qt", 2015);  //str="Qt:2015”

QString::arg()函式,提供更便利的方式格式化字串,支持Unicode,不用特別指定格式,並允許改變%n的順序。

QString str = QString("%1:%2").arg("Qt ").arg(2015);  //str="Qt:2015”

QString::toInt()、toDouble()函式,將字串轉成整數或浮點數。

QString str1 = "12";
QString str2 = "12.5";
int ret1 = str1.toInt();  //ret1=12
double ret2 = str2.toDouble();  //ret2=12.5

QString::number()函式,將整數或浮點數轉成字串。

QString str1 = QString::number(100);    //str1 = "100"
QString str2 = QString::number(100.5);  //str2 = "100.5"

QString::insert()函式,在原字串特定的位置插入另一個字串。 QString str = “hllo"; str.insert(1,QString(“e")); //str="hello"


QString::prepend()函式,在原字串開頭插入另一個字串。

QString str = "ello";
str.prepend(QString("h"));   //str="hello"

QString::replace()函式,用指定的字串取代原字串。

//replace(int position, int n, const QString &)
QString str = "aallo";
str.replace(0,2,QString("he")); //str="hello"

QString::trimmed()函式,移除字串兩端的空白。 QString str = " hello “; str.trimmed(); //str=" hello " str = str.trimmed(); //str= “hello"


QString::contains()函式,判斷一個字串是否出現過,第二個參數指定是否分辨大小寫,預設分辨大小寫。

QString str = "hello world";
bool test = str.contains("world",Qt::CaseSensitive);   //test=true

QString::startsWith()、endsWith()函式,判斷一個字串是否以某個字串做開頭或結尾。

QString str = "hello world";
bool test1 = str.startsWith("hello",Qt::CaseSensitive); //test1=true
bool test2 = str.endsWith("WORLD",Qt::CaseInsensitive); //test2=true

QString::compare()函式,比較兩個字串,如果前面字串小於後面返回負整數,等於的話返回0,大於的話返回整數。

QString str = "hello";
int ret = str.compare(QString("hi")); //ret=-4

QString str = "hello";
QChar char1 = str.at(0);  //char1="h"
QChar char2 = str[1];     //char2="e"

QString::isEmpty()函式,檢查字串是否為空字串。

QString str = "";
bool ret = str.isEmpty(); //ret=true

迭代器(Iterator)

Qt的容器類提供了兩種風格的迭代器:Java 風格和STL 風格,QLinkedList、QVector和QSet的迭代器介面與QList一樣;QHash迭代器的接口則和QMap一樣,基於效率考量,要是沒有對資料進行修改,使用唯讀迭代器較好。


Java風格迭代器: Java風格的迭代器比起STL風格的更方便,代價就是不如STL風格高效。

容器
唯讀迭代器讀寫迭代器
QList < T >
QQueue < T >
QListIterator< T >QMutableListIterator
< T >
QLinkedList< T >QLinkedListIterator< T >QMutableLinkedListIterator< T >
QVector< T >
QStack< T >
QVectorIterator< T >QMutableVectorIterator< T >
QSet< T >QSetIterator< T >QMutableSetIterator< T >
QMap< Key, T >
QMultiMap< Key, T >
QMapIterator< T >QMutableMapIterator< T >
QHash< Key, T >
QMultiHash< Key, T >
QHashIterator< T >QMutableHashIterator< T >

以下展示QList的迭代器使用,QLinkedList、QVector和QSet的迭代器介面與QList一樣,我們先對QList設定資料,接著用讀寫迭代器對QList的資料進行更改,最後再設定一新的唯讀迭代器顯現資料。

#include <QCoreApplication>
#include<QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QList<int> myList;
    myList<<1<<2<<3<<4<<5;
    QListIterator<int> i(myList);
    while(i.hasNext()){
        qDebug()<<i.next();
    }

    QMutableListIterator<int> mi(myList);
    while(mi.hasNext()){
        mi.next() += 5;
    }

    QListIterator<int> changed_i(myList);
    while(changed_i.hasNext()){
        qDebug()<<changed_i.next();
    }
    return 0;
}

以下展示QMap迭代器的使用,QHash迭代器的介面使用和QMap相同,我們先對QMap設定資料,接著用讀寫迭代器對QMap的資料進行更改,最後再設定一新的唯讀迭代器顯現資料,iterator::key()函式回傳QMap的key值,iterator::value()函式回傳相對值。

#include <QCoreApplication>
#include<QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QMap<QString,int> map;
    map.insert("mike",80);
    map.insert("john",70);
    QMapIterator<QString,int> i(map);
    while(i.hasNext()){
        qDebug()<<"  "<<i.key()<<"  "<<i.next().value();
    }

    QMutableMapIterator<QString,int> mi(map);
    if(mi.findNext(80)){
        mi.setValue(90);
    }

    QMapIterator<QString,int> changedMi(map);
    qDebug()<<"  ";
    while(changedMi.hasNext()){
        qDebug()<<" "<<changedMi.key()<<"  "<<changedMi.next().value();
    }
    return 0;
}

STL風格迭代器: 這種迭代器能夠兼容Qt和STL的通用算法,並且對速度進行了優化,迭代器同樣有兩種:一種提供唯讀訪問,一種提供讀寫訪問。

容器
唯讀迭代器讀寫迭代器
QList< T >
QQueue< T >
QList< T >::const_iteratorQList< T >::iterator
QLinkedList< T >QLinkedList< T >::const_iteratorQVector< T >::iterator
QVector< T >
QStack< T >
QVector< T >::const_iteratorQVector< T >::iterator
QSet< T >QSet< T >::const_iteratorQSet< T >::iterator
QMap< Key, T >
QMultiMap< Key, T >
QMap< Key, T >::const_iteratorQMap< Key, T >::iterator
QHash< Key, T >
QMultiHash< Key, T >
QHash< Key, T >::const_iteratorQHash< Key, T >::iterator

以下展示QList的迭代器使用,同樣基於效率考量,要是沒有對資料進行修改,使用唯讀迭代器較好,兩種迭代器寫法相同,只是將iterator改成const_iterator。

#include <QCoreApplication>
#include<QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QList<int> myList;
    myList<<1<<2<<3<<4<<5;
    QList<int>::const_iterator myIterator;
    for(myIterator = myList.constBegin(); myIterator != myList.constEnd(); ++myIterator) {
        qDebug() << *myIterator;
    }
}

以下展示QMap迭代器的使用,QHash迭代器的介面使用和QMap相同,我們先對QMap設定資料,接著用讀寫迭代器對QMap的資料進行更改,最後再設定一新的唯讀迭代器顯現資料。

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QMap<QString,int> map;
    map.insert("mike",70);
    map.insert("john",80);

    QMap<QString,int>::const_iterator i;
    for(i=map.constBegin();i!=map.constEnd();++i){
        qDebug()<<"  "<<i.key()<<"  "<<i.value();
    }

    QMap<QString,int>::iterator mi;
    mi = map.find("mike");
    mi.value() = 90;

    QMap<QString,int>::const_iterator changed_i;
    qDebug()<<"  ";
    for(changed_i=map.constBegin();changed_i!=map.constEnd();++changed_i){
        qDebug()<<"  "<<changed_i.key()<<"  "<<changed_i.value();
    }
    return 0;
}