2017年7月21日 星期五

Visual Studio 編譯 OpenCV 3 的擴充 Contrib 函式庫

OpenCV 3.2.0 的擴充 Contrib 函式庫如何編譯 與 使用 (Visual Studio)

關於一般的使用可以參考站內文章:OpenCV 3.2.0 vc14 如何安裝在 Visual Studio 2017
OpenCV 從第三版開始就把一些函式庫拆出來放到 Contrib 上如果要使用要自己重新編一次整份,這個編譯CPU不好可能要不少時間。
站長編譯的 VS2017 版本載點如下;點擊安裝即可,會自動部屬環境變數。
站內連結 懶人包http://charlottehong.blogspot.com/2017/12/opencv320-contrib.html

導航

  • 下載
  • CMAKE 產生編譯檔案
  • MAKE 編譯檔案
  • 建立檔案

下載

記得版本就下對opencv341要對opencv341 contrib,另外 VS2017 多次改版 cmake 要跟著升級不然make出來的東西VS不能編譯,會一堆失敗。
我第一次使用的時候版本錯了,一直出現錯誤找不到原因
cmake 會彈出 error configuring process, project files may be invalid 的錯誤信息
出現這個八成是版本錯了或者是沒有使用圖形介面選路徑(這個應該是cmake的bug)
版本從這裡選擇不要直接直接下
懶人包連結:
如果有安裝 Git 可以直接打指令省下解壓縮時間與空間

git clone -b 3.4.1 https://github.com/opencv/opencv

git clone -b 3.4.1 https://github.com/opencv/opencv_contrib
沒有的話按右邊下載zip檔案下來把兩個都下載下來

下載 cmake

根據自己的作業系統選擇正確的版本
安裝的時候創建一下捷徑與環境變數


CMAKE

打開之後最上面兩個路徑,第一個是opencv的路徑,如果是用 git 下載的就是選到下載的解壓後的資料夾,如果是從官方下載的選到source資料夾;
第二個是編譯後的路徑,選到你喜歡能夠找到的位置即可,記得創個空資料夾包起來。
(圖中我是從git下載的,如果下載zip資料夾名會多版本號)
先按左下角configure,然後跳出要你選編譯器,選你正在用的版本;注意圖中有個Win64建議選64位元的,效能較佳。
這邊第一次可能會卡一下,第一次時需要從網路下載缺失的套件,記得連網。
找到 ,輸入你下載的 opencv_contrib 中的 modules。
注意這裡不能貼上路徑會出錯,一定要按右邊按鈕選路徑(可能是bug)。
然後再按一下 configure ,第二次如果會出錯,看一下上面提到的。
接著按一下 Generate 就完成了,右邊 Open Project 可以直接打開;如果你有多個 Visual Studio 最好手動打該正確的版本,按一下打開專案,然後再找到路徑內的專案打開,再來別急著自己按。
到此完成第一階段了,已經產生需要編譯的檔案了


編譯生成檔案

對著專案按右鍵重建方案,準備好在按這裡會編譯很久且途中不能反悔關不掉;我自己實測i7 4790K約15~20分、i5 4460會到1小時。
這裡我是使用Debug模式重建,如果你需要使用Release模式編譯OpenCV需要在這裡選擇;兩個都需要就要個別編譯一次!
建好之後檢查一下底下有沒有錯誤
最後對著INSTALL按下僅限專案->僅建置INSTALL
完成編譯了,只剩下使用。

如何使用 OpenCV

之後找到install這個資料夾,留這個即可剩下的都可以砍了
把名稱更改為 OpenCV320_VC15 放到C槽底下
再來下載兩個批次檔:https://mega.nz/#F!B993FSKJ!NZ2YRRUGSb9r4JQQyHslpw
把AddPath放到這裡來運行(這個只是新增bin到環境變數)
把CreatList放到這裡點兩下運行,會多出兩個文字文件,先把他打開放著。(這個只是取得這個資料夾內所有 .lib 檔名稱)
打開你 Debug 可以看到這裡的檔案每個檔名結尾都有一個d表示是Debug模式用的,沒有Debug是給Release用的,因為這裡還沒編譯打開查看會是空的。


專案的建設

先開一份空專案
貼上測試的代碼:
/**********************************************************
Name :
Date : 2016/05/29
By   : CharlotteHonG
Final: 2016/05/29
**********************************************************/
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;

int main(int argc, char const *argv[]) {
    /* 畫布 */
    Mat img(270, 720, CV_8UC3, Scalar(56, 50, 38));
    /* 直線 */
    line(img, Point(20, 40), Point(120, 140), Scalar(255, 0, 0), 3);
    /* 實心方塊 */
    rectangle(img, Point(150, 40), Point(250, 140), Scalar(0, 0, 255), -1);
    /* 實心圓 */
    circle(img, Point(330, 90), 50, Scalar(0, 255, 0), -1);
    /* 空心橢圓 */
    ellipse(img, Point(460, 90), Size(60, 40), 45, 0, 360, Scalar(255, 255, 0), 2);
    /* 不規則圖形 */
    Point points[1][5];
    int x = 40, y = 540;
    points[0][0] = Point(0 + y, 50 + x);
    points[0][1] = Point(40 + y, 0 + x);
    points[0][2] = Point(110 + y, 35 + x);
    points[0][3] = Point(74 + y, 76 + x);
    points[0][4] = Point(28 + y, 96 + x);
    const Point* ppt[1] = { points[0] };
    int npt[] = { 5 };
    polylines(img, ppt, npt, 1, 1, Scalar(0, 255, 255), 3);
    /* 繪出文字 */
    putText(img, "Test Passed !!", Point(10, 230), 0, 3, Scalar(255, 170, 130), 3);
    /* 開啟畫布 */
    imshow("OpenCV Test By:Charlotte.HonG", img);
    waitKey(0);
    return 0;
}
修改屬性(先確認上方模式 Debug - x64 要是對的)
加入include目錄
加入lib目錄
加入lib檔案
目錄底下每個lib都要打上來(剛剛打開的文件夾可以一次複製全部)
然後就完成了
接下來你可以考慮把剛剛的模式改為Release模式在重新編譯一次;接著把檔案重新複製到C曹執行剛剛重複的步驟,就可以獲得兩個版本的檔案了,其中只需要注意引入的dll檔案,這裡我已經幫你把檔案清待分開了,在不同的模式輸入不同的清單即可。


其他補充 - 快速流程

  1. 下載指令替代
  2. Cmake部分其實可以用指令替代
  3. 指令開啟專案 -> CTRL+SH+B -> 換Release版本在一次
  4. 對著 INSTALL 右鍵, J, B
  5. install資料夾指令移動到C曹
  6. 指令配置bin環境變數
可以看參考這一篇,有做一半的流程
https://charlottehong.blogspot.tw/2018/03/opencv-341-contrib.html

2017年7月20日 星期四

CMD 批次檔 環境變數 長度太長 無法新增

CMD 批次檔 環境變數太長超過 1024 怎麼辦

換個工具吧QuQ,這個真的很惱人。不過還好這個工具從Win7開始就有了不用太擔心支援性。
除非長度限制之外還有一個很討人厭的是,我一直找不到可查看使用者變數的方法,終於讓我找到了,這個指令可以查看使用者的環境變數!
你可以透過cmd 輸入 powershell 啟動,如果不想離開 cmd 就在命令前加上 powershell。比如新命令 Get-Command 則輸入 powershell Get-Command。然後有 " 前要加斜線。
powershell的批次檔結尾是.ps1

環境變數設置與查看

這兩個函式可以用來區別使用者變數與系統變數
你也可以簡化他們為”User”, “Machine”,稍後會看到。

查看環境變數

[environment]::GetEnvironmentVariable("PATH", "User")
[environment]::GetEnvironmentVariable("PATH", "Machine")
還有一個比較簡單的方法
$env:path

設置環境變數

警告,記得先儲存自己的環境變數!儲存方法如下,幫你存到桌面。
cd ~;[environment]::GetEnvironmentVariable("PATH", "User") > Desktop/PATH_User.txt
cd ~;[environment]::GetEnvironmentVariable("PATH", "Machine") > Desktop/PATH_Machine.txt
設置環境變數
[System.Environment]::SetEnvironmentVariable("PATH", $Env:Path + ";C:bin", "User")
[System.Environment]::SetEnvironmentVariable("PATH", $Env:Path + ";C:bin", "Machine")
這個黑科技可以擺脫2048限制了,不過環境變數還是有總長度限制,能省則省。
還有一點就是,他終於有防呆了!不會新增重複的,自動避免新增一樣的環境變數。


cmd批次檔寫法如下

@Echo Off
Title AddPath - By:Charlotte.HonG
:: Date :2016/05/28
:: Final :2017/07/20

::===========================================================
::確認是否為管理員權限
call :IsAdmin
set appPATH=%~dp0bin
powershell [System.Environment]::SetEnvironmentVariable(\"PATH\", $Env:Path + \";%appPATH%;\", \"Machine\")
::===========================================================
Exit

:IsAdmin
@Echo Off
Reg.exe query "HKU\S-1-5-19\Environment"
If Not %ERRORLEVEL% EQU 0 (
  Cls
  Echo [權限不足] 需使用管理員權限開啟
  Pause & Exit
)
goto:eof


參考

使用 cmd or powershell 列出當前目錄檔案或特定副檔名

使用 cmd or powershell 列出當前目錄檔案或特定副檔名

這裡提供cmd與powershell兩個方法,另外如果要在cmd下執行powershell命令在最前方加入powershell指令即可。
須注意日文檔名的儲存的txt檔案,程序要讀檔會比較麻煩,能不用就不要用比較好。

CMD

列出當前檔案
dir "*.lib"
輸出到文件
dir "*.lib" /b /on > "Lib_Lists.txt"
日文檔名使用 cmd /u /c dir /b /on >list.txt
移動文件
MOVE %FILENAME% %LISTPATH%

批次檔

獲得 指定目錄 lib資料夾內的所有 .lib 檔案清單,並儲存到 批次檔所在目錄
::@Echo Off
Title CreatList - By:Charlotte.HonG

set FILENAME="Lib_Lists.txt"
set FILEPATH="lib"
set LISTPATH=%~dp0

cd %FILEPATH%
dir *.lib /b /on > %FILENAME%
MOVE %FILENAME% %LISTPATH%


powershell

$file=Get-ChildItem -Recurse -include "*.lib"
set-content $file.name -path Lib_List.txt
日文檔名則在第二行結尾增加 -Encoding Unicode

2017年7月10日 星期一

C++ 高斯模糊 詳細解析與源代碼

C++ 高斯模糊 詳細解析與源代碼

圖片上的高斯模糊有種兩方式,一種是二維的遮罩一種是一維的,不過就算是一維的X方向做一次Y方向再做一次也是可以相等於二維的。
一維相對來說比較好做、比較好講,也可以稍微避免邊界的問題,這裡主題就已一維為主。
步驟
  • 利用公式得出高斯矩陣
  • 利用高斯矩陣做捲積

公式

參考一下維基百科的公式:維基百科高斯模糊
這個公式有兩個變數是可以決定的,r 與 sigma(那個圈圈)
// 高斯公式
float GauBlur::gau_meth(size_t r, float p) {
    float two = 2;
    float num = exp(-pow(r, two) / (two*pow(p, two)));
    num /= sqrt(two*M_PI)*p;
    return num;
}
sigma越大會越模糊,r 是指矩陣位置
在實際產生之前還要決定矩陣要設多大,這可以自己設定也可以用公式算
// 計算矩陣長度
int mat_len = ((p-0.8) / 0.3+1.0) * 2.0;
比如說我設定成 sigma=1.6 那麼算出來需要長度為 7 的矩陣,當然你也可以自己決定要幾個,越多也會越模糊。

高斯矩陣

決定好矩陣長度之後你就可以利用公式算出來了
公式 r = 3 2 1 0 1 2 3
高斯矩陣 0 1 2 3 4 5 6
如此得到高斯矩陣。
得出之後還要把他們規一化,7個加起來總和是1
作法很簡單,讓矩陣個別除以他們的總和就可以了
寫成代碼像這個樣子
// 高斯矩陣
vector<float> GauBlur::gau_matrix(float p){
    vector<float> gau_mat;
    // 計算矩陣長度
    int mat_len = ((p-0.8) / 0.3+1.0) * 2.0;
    if (mat_len % 2 == 0) {++mat_len;}
    // 一維高斯矩陣
    gau_mat.resize(mat_len);
    float sum=0;
    for(int i=0, j=mat_len/2; j < mat_len; ++i, ++j) {
        float temp;
        if(i) {
            temp = gau_meth(i, p);
            gau_mat[j] = temp;
            gau_mat[mat_len-j-1] = temp;
            sum += temp += temp;
        } else {
            gau_mat[j]=gau_meth(i, p);
            sum += gau_mat[j];
        }
    }
    // 歸一化
    for(auto&& i : gau_mat) { i /= sum; }
    return gau_mat;
}
到這裡為止算出來的矩陣是
0 = 0.0441465
1 = 0.117223
2 = 0.210611
3 = 0.256038
4 = 0.210611
5 = 0.117223
6 = 0.0441465
你也可以直接抄這個矩陣,直接拿去用

捲積

這裡需要引入一個名詞遮罩
假如說現在有9宮格,鍵盤上的,我的遮罩大小是 1x3 橫的,位置是5,那麼遮罩的矩陣是
4 5 6
依此類推,現在你讀入一張圖,剛剛的高斯矩陣長度多少遮罩就多少,需要你罩住長度的7的矩陣,位置從0跑到最後。
假如罩到邊邊,那麼缺的地方可以補邊緣或是鏡像另一邊開始算回來,都可以有很多方式,一般最簡單的建議補邊邊就好。
一樣是剛剛的例子舉例假如我選中鍵盤9宮格 7 那麼遮罩的矩陣是
7 7 8
依此類推。
罩住之後就可以開始做捲積了,記得不要再同一個陣列做要寫到另一個陣列不然會被影響。然後加總的數值型態要是float或double不然出來圖會偏黑。
double sum = mask[0] gau[0] + mask[1] gau[1]…;
把它們各相乘最後再相加寫到新的陣列就可以完成了。

源代碼

// 高斯模糊
class GauBlur{
public:
    static void raw2GauBlur(vector<unsigned char>& img_gau,
        vector<unsigned char>& img_ori,
        size_t width, size_t height, float p);
private:
    static vector<float> gau_matrix(float p);
    static float gau_meth(size_t r, float p);
};
// 高斯模糊
void GauBlur::raw2GauBlur(vector<unsigned char>& img_gau,
    vector<unsigned char>& img_ori,
    size_t width, size_t height, float p)
{
    // 設定正確的大小
    img_gau.resize(img_ori.size());
    // 緩存
    vector<double> img_gauX(img_ori.size());
    // 高斯矩陣與半徑
    vector<float> gau_mat = gau_matrix(p);
    const int r = gau_mat.size()/2;
    // 高斯模糊 X 軸
    const size_t r = gau_mat.size() / 2;
    for (unsigned j = 0; j < height; ++j) {
        for (unsigned i = 0; i < width; ++i) {
            double sum = 0;
            for (unsigned k = 0; k < gau_mat.size(); ++k) {
                int idx = i-r + k;
                // idx超出邊緣處理
                if (idx < 0) {
                  idx = 0;
                } else if (idx >(int)(width-1)) {
                  idx = (width-1);
                }
                sum += img_ori[j*width + idx] * gau_mat[k];
            }
            img_gauX[j*width + i] = sum;
        }
    }
    // 高斯模糊 Y 軸
    for (unsigned j = 0; j < height; ++j) {
        for (unsigned i = 0; i < width; ++i) {
            double sum = 0;
            for (unsigned k = 0; k < gau_mat.size(); ++k) {
                int idx = j-r + k;
                // idx超出邊緣處理
                if (idx < 0) {
                  idx = 0;
                } else if (idx > (int)(height-1)) {
                  idx = (height-1);
                }
                sum += img_gauX[idx*width + i] * gau_mat[k];
            }
            img_gau[j*width + i] = sum;
        }
    }
}
// 高斯公式
float GauBlur::gau_meth(size_t r, float p) {
    float two = 2;
    float num = exp(-pow(r, two) / (two*pow(p, two)));
    num /= sqrt(two*M_PI)*p;
    return num;
}
// 高斯矩陣
vector<float> GauBlur::gau_matrix(float p){
    vector<float> gau_mat;
    // 計算矩陣長度
    int mat_len = ((p-0.8) / 0.3+1.0) * 2.0;
    if (mat_len % 2 == 0) {++mat_len;}
    // 一維高斯矩陣
    gau_mat.resize(mat_len);
    float sum=0;
    for(int i=0, j=mat_len/2; j < mat_len; ++i, ++j) {
        float temp;
        if(i) {
            temp = gau_meth(i, p);
            gau_mat[j] = temp;
            gau_mat[mat_len-j-1] = temp;
            sum += temp += temp;
        } else {
            gau_mat[j]=gau_meth(i, p);
            sum += gau_mat[j];
        }
    }
    // 歸一化
    for(auto&& i : gau_mat) { i /= sum; }
    return gau_mat;
}
使用方式如下(須自己讀入一張灰階圖到 raw_img 陣列內)
vector<unsigned char> gau_img;
GauBlur::raw2GauBlur(gau_img, raw_img, height, width, p);