2018年6月6日 星期三

C / C++ HDR rgbe 讀檔 與 色調映射(toneMapping) 實作範例代碼

C / C++ HDR rgbe 讀檔 與 色調映射(toneMapping) 實作範例代碼

以前做的作業整理起來順便發表上來,使用的方法是最簡單的能看能主的方法,並沒有任何優化。

參考論文:

細節與對比強化之高動態範圍影像顯示方法
High Dynamic Range Image Display with Detail and Contrast Enhancement
本篇不是實現上述論文,實現的是他提到的先前的方法
基於這個公式做色調映射:
色調映射有分全域與局域(本文實作的是全域),全域就是同一個公式對整張圖的每個點做,比較簡單迅速,缺點是可能會有某些地方表現不好,通常是最亮或是最暗的地方。需要比較好看的話要做局域色調映射,針對特別亮或暗的地方做不同處理。

使用函式庫:

流程

  1. 讀取 rgbe 轉換為 rgb
  2. 轉換彩色模型至 Yxy
  3. 對亮度通道做色調映射(tone mapping)
  4. 色彩模型轉回 rgb
  5. 進行 gama 校正 (微軟 Win10 設置為 2.2)
  6. 完成 global tone mapping


讀取 rgbe 轉換為 rgb

使用函式庫 rgbe 讀取,如果要重刻過程其實還蠻麻煩的~
使用的資料結構
struct basic_rgbeData {
    int width;
    int height;
    rgbe_header_info info;
    vector<float> img;
};
讀取的程式
void rgbeData_read(basic_rgbeData& hdr, string name) {
    FILE* hdrFile;
    fopen_s(&hdrFile, name.c_str(), "rb");

    // 讀檔頭
    RGBE_ReadHeader(hdrFile, &hdr.width, &hdr.height, &hdr.info);
    // 要求空間
    hdr.img.resize(hdr.width*hdr.height*3);
    // 讀rgbe
    RGBE_ReadPixels_RLE(hdrFile, hdr.img.data(), hdr.width, hdr.height);

    fclose(hdrFile);
}
讀進來之後就可以獲得圖像的長寬與 rgb 值了,這裡的rgb值會是小數點介於 0~1 之間,可以直接全部*255,然後把圖片顯示出來就可以看到黑黑的圖像了。
大於255的就變成255,小於0的就變成0

轉換彩色模型至 Yxy

把色彩模型從 rgb 轉換至 Yxy,實際上轉了兩次 rgbe -> yxz -> Yxy,因為公式可以合併並不衝突,我把它寫在一起了。(Yxy只是將ZXY轉換到坐標系上)
這個做是為了分離出光源與彩度,需要做處理的是光源,彩度則維持原本的樣子。
void rgb2Yxy(const float* src, float* dst, int size) {
//#pragma omp parallel for
    for(int i = 0; i < size; ++i) {
        float a, b, c;
        a = (0.412453) * src[i*3 + 0]+
            (0.357580) * src[i*3 + 1]+
            (0.180423) * src[i*3 + 2];

        b = (0.212671) * src[i*3 + 0]+
            (0.715160) * src[i*3 + 1]+
            (0.072169) * src[i*3 + 2];

        c = (0.019334) * src[i*3 + 0]+
            (0.119193) * src[i*3 + 1]+
            (0.950227) * src[i*3 + 2];
        dst[i*3 + 0] = b;
        dst[i*3 + 1] = a / (a+b+c);
        dst[i*3 + 2] = b / (a+b+c);
    }
}
#pragma omp parallel for 是平行算的標記,下面接著的for迴圈會被展開來平行運算。

對亮度通道做色調映射(tone mapping)

這裡就是全文的重點了,來自於上面介紹的公式。
其中的 dmax 與 b 來源是依據論文所提的建議設置
size是圖像的指標dst的總長度
#define HDR_dmax 100.0
#define HDR_b 0.85

void globalToneMapping(float* dst, int size, float dmax, float b)
{
    constexpr int dim = 3; // 幾個通道
    constexpr int rgb = 0; // 選擇哪個通道

    float maxLum = dst[rgb];
    for(unsigned i = 1; i < size; ++i) {
        if(dst[i*dim+rgb] > maxLum)
            maxLum = dst[i*dim+rgb];
    }

    float logSum = 0.0;
    for(int i = 0; i < size; ++i) 
        logSum += log(dst[i*dim+rgb]);

    const float logAvgLum = logSum/size;
    const float avgLum = exp(logAvgLum);
    const float maxLumW = (maxLum / avgLum);
    const float coeff = (dmax*float(0.01)) / log10(maxLumW+1.0);

#pragma omp parallel for
    for(int i = 0; i < size; ++i) {
        auto& p = dst[i*dim+rgb];
        p /= avgLum;
        p = log(p+1.0) / log(2.0 + pow((p/maxLumW),(log(b)/log(0.5)))*8.0);
        p *= coeff;
    }
}

色彩模型轉回 rgb

跟剛剛的公式一樣只是反轉回來RGB模型
void Yxy2rgb(const float* src, float* dst, int size){
//#pragma omp parallel for
    for(int i = 0; i < size; ++i) {
        float a, b, c, newW;
        newW = src[i*3 + 0] / src[i*3 + 2];
        a = src[i*3 + 0];
        b = newW * src[i*3 + 1];
        c = newW - b - a;

        dst[i*3 + 0]  =  float(3.240479)*b;
        dst[i*3 + 0] += -float(1.537150)*a;
        dst[i*3 + 0] += -float(0.498535)*c;

        dst[i*3 + 1]  = -float(0.969256)*b;
        dst[i*3 + 1] +=  float(1.875992)*a;
        dst[i*3 + 1] +=  float(0.041556)*c;

        dst[i*3 + 2]  =  float(0.055648)*b;
        dst[i*3 + 2] += -float(0.204043)*a;
        dst[i*3 + 2] +=  float(1.057311)*c;
    }
}
轉回來之後就已經映射完畢可以輸出來看看,與一開始相比過亮與過暗的地方已經修正完畢可以看到細節,但是整體偏暗許多需要再進行下一步修正。

進行 gama 校正

最後要將亮度調整到作業系統適合的參數,圖像一般都在 1.0 ,而Windwos系統使用的是 2.2 要將它修正到正確的參數即可。
gama參數這個資訊可以從結構裡面我們一直沒用到參數 rgbe_header_info info; 裡獲得。
#define HDR_gama 2.2

void gama_fix(float* dst, int size, float gam) {
    const float fgamma = (0.45/gam)*2.0;
    float slope = 4.5;
    float start = 0.018;
    // 判定係數
    if(gam >= float(2.1)) {
        start /= ((gam - 2) * float(7.5));
        slope *= ((gam - 2) * float(7.5));
    } else if(gam <= float(1.9)) {
        start *= ((2 - gam) * float(7.5));
        slope /= ((2 - gam) * float(7.5));
    }
    // 校正像素
#pragma omp parallel for
    for (int i = 0; i < size*3; i++) {
        if(dst[i] <= start) {
            dst[i] = dst[i]*slope;
        } else {
            dst[i] = float(1.099)*pow(dst[i], fgamma) - float(0.099);
        }
    }
}

完成 global tone mapping

再來結構的裡的圖像就可以使用摟,把它印出來。
轉出來的數值還是 0~1 要把他們 *255
然後大於255的就變成255,小於0的就變成0
hdr.bmp("resultIMG/HDR_IMG.bmp");


專案原始碼

未封裝測試代碼可以參考 OpenHRD.cpp 中的 testMapping() 如下,比較容易讀懂在寫什麼。
rgbeData_writeBMP 是將 float* 輸出成 *.bmp 圖檔到硬碟上使用函式庫 OpenBMP
void testMapping(string name) {
    // read file
    basic_rgbeData hdr;
    rgbeData_read(hdr, name);
    rgbeData_info(hdr);
    //rgbeData_writeBMP(hdr, "resultIMG\HDR_non.bmp");

    // Mpping
    int imgSize = rgbeData_size(hdr);
    vector<float> Yxy(imgSize*3);

    rgb2Yxy(hdr.img.data(), Yxy.data(), imgSize);
    globalToneMapping(Yxy.data(), imgSize);
    Yxy2rgb(Yxy.data(), hdr.img.data(), imgSize);
    //rgbeData_writeBMP(hdr, "resultIMG\HDR_mapping.bmp");

    cout << hdr.info.gamma << endl;
    gama_fix(hdr.img.data(), imgSize, 2.2);
    rgbeData_writeBMP(hdr, "resultIMG/HDR_IMG.bmp");
}

這裡是完整的專案已經封裝成完整的物件:OpenHDR for VS2017

2018年6月4日 星期一

RT-N12D1 刷 DD-WRT 紀錄

RT-N12D1 刷 DD-WRT 紀錄

不知道為什麼最近去 DDWRT 的網站都不能搜尋,顯示資料庫維修中過好久了都沒好。
紀錄一下順便備份一份檔案

載點

使用

具官方的說法是
  1. ASUS 3.0.0.4.374.813 官方韌體刷 14896.trx
  2. 然後會刷壞變磚,此時壓住RESET然後開電等15~20秒,進入救援模式燈號是全亮5秒慢閃(這是必要的)。
  3. 然後再用救援模式刷入 18774.bin 到這邊就成功了
  4. 最後刷好之後再由 DDWRT 18774 升級至最新的DDWRT韌體 33215.bin。
據官方說法這是唯一可行的辦法,其他方法都不行。

2018年6月2日 星期六

華雲 ASUSTOR NAS 如何安裝 Jdownload2

華雲 ASUSTOR NAS 如何安裝 Jdownload2

安裝的過程不是很順利,弄好一段時間,後來才發現疑似沒有用的網站其實是說明…一開始被我略過了他前面沒說到到重點我就XX了
http://forum.asustor.com/viewtopic.php?f=158&t=7678#p23667
好了除了從網站上安裝之外還不能啟動,要手動SSH連進去才可以,如果是使用Win10直接打開 powershell 預設就有 ssh 可以用,如果不是或者是你使用舊版的Win10,這些並不是沒有,只是預設是關閉的,建議從網路找現成的工具比較快就不用去研究怎麼打開了。
然後在NAS這邊也要打開SSH功能才可以接入,可以直接從探照燈搜尋SSH
就會看到功能選項,再來打開這項功能
然後輸入你的NAS的使用者名稱與所在IP,IP也可以是網路芳鄰上的英文名字。
username要使用有管理員權限的,這邊就建議你直接用admin
ssh username@nas-ip
再來需要你手動打 yes 連入,然後輸入密碼
完畢之後需要到JD的資料夾內使用輸入底下密碼
cd /usr/local/AppCentral/jdownloader
到這邊有東西就代表你有裝好,然後先到JD的網站註冊一個帳號,屆時會用WEB的方式操控JD而不是從NAS上。
確實註冊好可以在網站上登入才進行下一步,不然等一下設置中要用到
然後我們開始初始化JD
./init.sh
大概是按三次 y 之後會需要你輸入帳密,如下圖的地方。
到這邊輸入剛剛註冊的帳密,然後就成功了
記得輸入密碼成功之後不會有任何提示,畫面如果卡著,自己手動按CTRL+C退出。
(就是說你看不到下圖提示,直接跳過這張圖到下一個步驟了)
接下來的關鍵字是這個看到就代表裝完了,然後要使用 ctrl+c 離開教學上有警告,不可以用其他方式離開,如直接關終端機等等,然後你就會看到成功訊息。
回去網頁打開JD吧 (安裝完畢時預設是關閉的)
不過這個打開其實沒什麼,我本來以為會在WEB內看到JD操作畫面
結果是連接到網頁上
這個電腦版的也可以使用,個人用起來是很卡很慢,很難用@u@
不過能用就好啦~至少確定可以在NAS上下載了XDD
另外手機也有 jdownload2 的APP可以連接歐,可以下載來玩玩
https://play.google.com/store/apps/details?id=org.appwork.myjdandroid&hl=zh_TW
一樣是登入帳密之後就可以控制了,採用的方式是JD自己有伺服器,你會像 teamview 那樣連他們的伺服器,所以你的NAS就算不公開在網路上(比較安全),也可以透過 JD 輸入網址然後在NAS上下載,這點我覺得非常重要XD




2021/12/30 更新

今天因為不知道的原因無法更新只好解除安裝重裝了,所以流程又重新跑了一次,最後結尾的地方有點不同,更新一下。
新版的Jdownloder多了一個版本,另一個版本可以直接遠端圖形介面,壞處是我找了半天就是不能更改儲存位置,最後就砍了重新用回這個指令版的。
在輸入帳號密碼之後畫面會卡住,我第一次等太久以為壞了直接按CTRL+C離開了。後來APP點擊無法啟動以為失敗了,解除安裝重用一次,出門吃飯回來看到下圖是會出現100%的。

然後在上圖按下CTRL+C離開,之後可以看到下圖成功畫面。


離開之後從點擊APP圖示會出現服務沒啟動,但是直接點擊下方網址,可以看到已經上線可以用了。

會看到這個裝置上線了,這邊結尾是使用者帳號。用管理者帳號好處是避免之後砍掉使用者帳號導致JD壞了,以及它可以存取任何使用者的資料。不過要注意預設位置會存在admin資料夾裡記得自己更改儲存位置。
如果要儲存到其他硬碟,比如說第二顆硬碟路徑是這樣的
(路徑可以直接在SSH裡退到最前面查路徑)
/volume2/volume2

2018年6月1日 星期五

NAS 架設 git server 實戰範例

NAS 架設 git server 實戰範例

其實只要有 SSH+git 就可以架設了,貌似也沒有什麼限制 (git沒有分客戶端跟伺服器端)

伺服器端

這邊要先開好 SSH 可以連進來以及裝好git,簡單來說就是你可以連進這一台並且輸入git命令有東西,這樣就可以了。
再來是環境的部分,使用者建議多件一個名為 git 的使用者,如果是其他名字待會客戶端指令要跟著改。
接下來要開始建立伺服器倉庫,指令比較不一樣,工作目錄就直接使用者文件底下
git init --bare Repos.git
指令下完就在這裡產生一個 Repos.git 的資料夾,到時候倉庫會同步在這裡,以github來說目前的階段就是
你按下新倉庫然後輸入倉庫名稱,不初始化任何東西,此時頁面跳出教你如何上傳指令
的這個狀態。

客戶端

接下來照著 github 上的教學打就可以了,先初始化本地git並且至少有一個提交紀錄。
mkdir Repos
cd Repos

echo "# Repos" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin git@CHG-NAS:Repos.git
git push -u origin master
然後就傳上去了~就這麼簡單
要改的部分是 git@CHG-NAS:Repos.git
git = 使用者名稱
@CHG-NAS = 服務器IP
:Repos.git = 預設從使用者目錄開始算

2018年5月31日 星期四

ubuntu 16.04 安裝 cuda 9.0

ubuntu 16.04 安裝 cuda 9.0

驅動程式可以不安裝過程會自己安裝,先安裝也會重新安裝cuda選用的版本,整個流程跑完要手動重新開機。
因為有安裝驅動,建議下面流程跑完就馬上重新開機,不然用一用可能會當機。安裝驅動之後重新開機第一次會比較久,等一下會黑畫面什麼都沒有很像死機了。
過程涉及安裝驅動~如果有發生什麼衝突可能導致系統崩潰無法開啟,這點要注意一下~

可以不用點有包含再底下指令(cuda版本有升級文章還沒更新就要手動改版本號)
安裝指令
# 加入驅動鏡像站
sudo add-apt-repository ppa:graphics-drivers/ppa -y

# 升級系統
sudo apt-get update
sudo apt-get upgrade -y

# 安裝 cuda
cd ~/Downloads
sudo wget -c https://developer.nvidia.com/compute/cuda/9.0/Prod/local_installers/cuda-repo-ubuntu1604-9-0-local_9.0.176-1_amd64-deb
sudo dpkg -i cuda-repo-ubuntu1604-9-0-local_9.0.176-1_amd64-deb
sudo apt-key add /var/cuda-repo-9-0-local/7fa2af80.pub
sudo apt-get update
sudo apt-get install cuda -y

# 加入函式庫
export PATH=/usr/local/cuda-9.0/bin${PATH:+:${PATH}}
到這邊就裝好了,還要配置他的bin跟lib位置,否則直接打 nvcc 會出現找不到
方法是參考官方說明網站:docs.nvidia.com
# 函式庫標頭檔路徑
export PATH=/usr/local/cuda-9.0/bin${PATH:+:${PATH}}

# 函式庫路徑(這個好像不用)
export LD_LIBRARY_PATH=/usr/local/cuda-9.0/lib64 ${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
這樣就配置好了可以打
nvcc --version
只不過剛剛的配置方法只有對這個終端機有用,換終端機或是重開就沒了
想要真正加入的話把那兩行寫入 vi .bashrc 這個文件裏面開機自動執行就好了
vi .bashrc
加入最尾端或是隨意找個地方寫也可以,然後重新登入再打測試版本的指令就有了。
最後測試一下到底有沒有裝對。
這邊記得先重開機一次否則測試結果會失敗歐
git clone https://github.com/hunandy14/cuda_copyArr
cd cuda_copyArr
make run
看一下有沒有跑出 test ok 有的話就是一切都正常了,包含驅動。(失敗的話重新開機再試試看)
這是一個簡單的cuda程式,其中我有寫一個比較容易的呼叫使用記憶體的函式,適合新手學習使用。
寫文章測試的時候 cuda9.0 選用的版本 384 而當前最新版本是 390 ,建議不要更動這個版本裝好就別升級了,可能會發生一些無法預測的錯誤。