分類: #C

  • 如何使用 CPP 加速 DCT 運算

    如何使用 CPP 加速 DCT 運算

    本篇文章來自 想知道網戀對象有沒有修圖嗎?試試看這款修圖偵測機器人! 的續篇,

    因為 Python 的方法實在是太慢了,所以我一直在尋求加速的方法。
    俗話說得好:
    要看一個人怎麼做立委,就要看他怎麼做立委!

    不是,我是說有些人可能不適合做立委、適合做總統!

    舉個例子:
    拿 Python 去做文字處理就很開心,但是拿 C/CPP 去做文字處理你就準備腦血栓;
    反之拿 Python 做數學運算也會慢到中風,但是拿 C/CPP 做數學運算就風馳電掣。

    所以我用 CPP 實現了需要數學運算的 DCT 方法,

    主要是使用 extern “C” 方法來與 Python 對接,在 Python 那邊設定好輸出入的參數。

    這邊比較需要注意的是因為我選用的讀圖方式是 OpenCV 的 CPP 函式庫,

    所以寫 Makefile 的時候需要注意把 OpenCV 包進來。
    在編譯的過程中 Makefile 也會發生抓不到 g++ 的時候
    (想好好編譯真難 😇)
    總之做了一點 Soft-link 還有 Dirty work 後終於能正確編譯了。
    因為我是用”Single Thread”、”Mask” 的方法來實現 DCT 變換的,
    所以讓我們來看看與 Python 版 與 CPP 版 比較效果如何:

    CPP 版的運算速度比 Python 版快了近六倍,
    有夠優質!

    相關開源我更新在:
    https://github.com/wuyiulin/GraphAppBot

    想要測試一下這個服務:

    https://t.me/DynamicGraphApp_bot

    如果有任何問題歡迎聯絡我:
    wuyiulin@gmail.com

  • 使用 libconfig 使純 C 語言支援 config.ini 讀寫教學

     

    最近公司開始有新想法,加上 ESP32 要讀寫資料,
    開始在研究怎麼在純 C 裡面讀寫 config.ini。

    因為經手的專案都搭配 Makefile 編譯,
    所以本篇也會一併紀錄 Makefile 設定眉角。

    我的環境是 Ubuntu 22.04,
    apt 裡面沒辦法直接裝 libconfig,
    於是首先先下載 libconfig 的壓縮包:

    前往 libconfig 的 Github 頁面下載

    從別人那邊轉貼的安裝流程:

    # 解壓縮
    tar -zxvf libconfig-你下載的版本編號.tar.gz
    
    # 進入工作資料夾
    
    cd libconfig-你下載的版本編號
    ./configure
    
    # 編譯
    make -j8
    
    # 檢查編譯有沒有壞掉
    make check
    
    # 開始安裝
    sudo make install
    
    # 複製檔案到你想要的地方,這邊一定要 sudo 不然 .so 會進不去
    # 原版教學
    sudo cp -d ./lib/libconfig* /usr/lib
    # 想用 Makefile 包進專案
    sudo cp -d ./lib/libconfig* /usr/include
    
    
    # 檢查安裝路徑是否正確
    sudo ldconfig -v
    

    在你的 Makefile 做兩件事情
    1. 確定 CFLAG 裡面包含 /usr/include(通常會包)
    2. -LDFLAG 後面加上 -lconfig 

    接下來就能讀寫 config.ini

    接下來就能

    config_t cfg;
    config_setting_t *setting;
    config_init(&cfg);
    
    //讀取整份文件
    config_read_file(&cfg, "config.cfg");
    
    //讀取特定的 value
    const char *value;
    config_lookup_string(&cfg, "section1.key1", &value);
    
    //寫入特定的 value 至暫存
    setting = config_lookup(&cfg, "section1.key2");
    config_setting_set_string(setting, "new_value");
    
    //將暫存寫入文件
    config_write_file(&cfg, "config.cfg");

    要特別注意的是這個套件支援的 ini格式有點不一樣
    這格式長這樣:

    section:
    {
        key1 = value1
        key2 = value2
        key3 = value3
    }
    

    完美 謝謝指教

  • 解決 OpenCV 編譯後不定時崩潰、失效等問題

    解決 OpenCV 編譯後不定時崩潰、失效等問題

     

    這邊提供 OpenCV 編譯後崩潰的可能解決方法:

    我的環境是 OpenCV 4.5.4、Ubuntu 22.04,

    並使用 g++ 11.4.0 編譯我的專案。

    我遇到的情境問題是,我得到一包 C/C++ 的專案,

    裡面用 Makefile 來整合編譯專案,裡面包含我自己寫的一段高斯濾波程式碼。

    這貨整包編譯時沒有出錯;

    獨立把高斯濾波程式碼放到另一個編譯、運行也都沒有出錯。

    但當我在源碼裡面運行這段高斯濾波程式碼時”有機率”會出錯:

        int ksize = 3;
        cv::Size size = image.size();
        int width = size.width;
        int height = size.height;
        cv::Mat blurred_image(height, width, CV_8UC1, cv::Scalar::all(0));
        cv::GaussianBlur(grey, blurred_image, cv::Size(ksize, ksize), 0, 0);
    

    然而我給定的高斯核大小為 3×3,

    非常奇怪,程式會跳說高斯核定義不是奇數,所以不合法:

    error: (-215:Assertion failed) ksize.width > 0 && ksize.width % 2 == 1 && 
    ksize.height > 0 && ksize.height % 2 == 1 in function 'createGaussianKernels'
    

    除此之外,當我想使用旋轉圖片、在圖片上畫點的功能也全部失效,但是編譯又沒有出錯,

    這件事情真的是非常奇怪。

    因為是運行時錯誤,於是我先用 GDB 檢查了函式是否重複定義:

    然而並沒有,這就奇了怪了。

    後來我開始埋 log 想辦法抓鬼,也完全抓不到。

    解決方法:

    把專案的 Makefile 打開,有關於 OpenCV 的部份改寫,

    讓 Makefile 繞過 pkg-config ,手動給定 OpenCV.hpp 還有 libopencv_XXX.so 的路徑。


    如果我的 OpenCV.hpp 放在 /usr/include/opencv4/opencv2 底下,

    那就把 CFLAGS+= 裡面加入 -I/usr/include/opencv4;

    libopencv_XXX.so 放在 /usr/lib/x86_64-linux-gnu 下面,

    那就把 LDFLAGS += 裡面加入 -L/usr/lib/x86_64-linux-gnu。


    原版:

    ifeq ($(OPENCV), 1) 
    COMMON+= -DOPENCV
    CFLAGS+= -DOPENCV
    LDFLAGS+= `pkg-config --libs opencv4`
    COMMON+= `pkg-config --cflags opencv4`
    endif
    



    改寫成:

    ifeq ($(OPENCV), 1) 
    COMMON+= -DOPENCV
    CFLAGS+= -DOPENCV -I/usr/include/opencv4
    LDFLAGS+= `pkg-config --libs opencv4` 
    LDFLAGS+= -L/usr/lib/x86_64-linux-gnu -lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_imgcodecs
    COMMON+= `pkg-config --cflags opencv4`
    endif
    


    完美解決

    雖然我另外一份獨立的 CPP 檔案能夠用 pkg 找到 OpenCV 也能編譯,

    pkg PATH 裡面也有 OpenCV,暫時不知道哪裡耦合到了,

    先留紀錄改天遇到再來深究。

    若有錯誤請聯絡我 – wuyiulin@gmail.com

  • 如何利用 Intel 內顯資源進行 OpenCL 開發

    如何利用 Intel 內顯資源進行 OpenCL 開發

     

    這篇主要介紹 Linux/Windows 兩個平臺使用 Intel 內顯資源開發 OpenCL 的解決方案,
    並且不用重新編譯核心,不像這篇太 Hardcore 了xD

    首先你當然要確定自己的 Intel CPU 有沒有支援 OpenCL?

    先參考 Supported APIs for Intel® Graphics

    但是只是參考就好,因為我在裡面用一塊號稱支援 OpenCL 2.0 的晶片支援了 OpenCL 3.0。

    我認為 Intel 並不知道自己在做什麼。

    LINUX

    筆者在此使用 Ubuntu 22.04 做為開發環境,

    在此 OS 下你只需按照這篇安裝 intel i915 驅動,

    然後驗證驅動有沒有裝好?

    sudo apt-get install clinfo
    
    clinfo -l

    如果有裝好應該會像筆者的畫面這樣:

    接著你想要用 gcc 或 g++ 開發都沒問題,

    甚至用 make 編譯專案也可以直接車過去,這就是 UNIX 帶原生編譯器的好處。

    Windows

    筆者在此使用 Windows 10 做為開發環境,

    在 Windows 不用特別去裝 intel 驅動,理論上一定會自行下載。


    但 Windows 因為沒有原生編譯器的優勢,所以你必須選擇編譯器流派。

    如果你沒有要編譯整個專案,只想自己寫一點東西來驗證,

    我推薦使用 MinGW64 就好。

    MinGW64 只要再額外安裝 libheaders 便可以執行後續驗證。

    使用這份 .C程式碼來驗證:

    #include 
    #include 
    
    int main() {
        cl_uint platformCount;
        clGetPlatformIDs(0, nullptr, &platformCount);
    
        if (platformCount == 0) {
            std::cerr << "No OpenCL platforms found." << std::endl;
            return 1;
        }
    
        cl_platform_id* platforms = new cl_platform_id[platformCount];
        clGetPlatformIDs(platformCount, platforms, nullptr);
    
        std::cout << "Number of OpenCL platforms: " << platformCount << std::endl;
    
        for (cl_uint i = 0; i < platformCount; ++i) {
            char platformName[128];
            clGetPlatformInfo(platforms[i], CL_PLATFORM_NAME, sizeof(platformName), platformName, nullptr);
    
            std::cout << "Platform " << i + 1 << ": " << platformName << std::endl;
        }
    
        delete[] platforms;
    
        return 0;
    }
    
    

    如果你需要 CMake 整份專案,

    那先跟我大喊一聲:Fuck you Intel!

    原因是網路上安裝 Intel OpenCL 的教學大多都需要這份:

    ​Intel SDK for OpenCL applications​

    但是這份已經停更了。

    根據 Intel 官方表示後續會分散在各 OneAPI 部件裡面支援,

    所以我們會被引導到這個 Intel 官方文件頁面:

    Intel Tools for OpenCL™ Applications

    我試過了都沒用,不要浪費時間跟 Intel 官方文件在那邊鬧。

    他們最近不知道想推 SYCL 還是什麼東西,總之沒有支援。

    我會說沒有支援的原因是因為:

    你要能 CMake 一份專案最主要要引用兩份東西,

    一份叫做 OpenCL.lib,一份叫做 CL.h or CL.hpp。

    按照 Intel 官方文件裝完上述套件都找不到 CL.h or CL.hpp,

    就算找來 KhronosGroup 版本的 CL.h 代打也會有問題,

    天知道 Intel 怎麼寫他們的 OpenCL.lib?

    另外一個不推 Intel 解決方案的原因是近年來他們都綁 MSVC,

    代表我要載又肥又呆的 Virtual Studio,個人不是很喜歡。

    怎麼辦呢?

    打不贏就加入!

    我在開發死線前尋尋覓覓,發現敵人就在本能寺!

    發現敵人的敵人就是朋友,AMD 有做這個欸?

    我能不能用 AMD 的文件驅動 Intel CPU 呢?

    哇靠!可以!

    大家只要下載這個來安裝,並記住安裝路徑。

    他會提供 CMake 最需要的 OpenCL.lib 及 CL.h。

    接著在你的 CMakeLists.txt 裡面修改,

    通常上面兩行要新增 OpenCL.lib 及 CL.h 的路徑,

    下面兩行應該本來就有,新增一下就好。

    find_path(OPENCL_INCLUDE_DIRS CL/cl.h PATHS /path/to/opencl/include)
    find_library(OPENCL_LIBRARIES OpenCL PATHS /path/to/opencl/lib)
    
    include_directories(${OPENCL_INCLUDE_DIRS})
    target_link_libraries(YourExecutable ${OPENCL_LIBRARIES})
    

    這樣應該就能成功編譯專案了!

    如果本篇內容有誤歡迎聯絡我:

    wuyiulin@gmail.com

    謝謝大家!

    REF.
    OpenCL入门一:Intel核心显卡OpenCL环境搭建

    How to install OpenCL on Windows

    Intel Arc 独显Ubuntu安装指南

  • [面試] 瑞鼎 – 觸控演算法工程師面試紀錄

    2023 – 11

    瑞鼎 – 觸控演算法工程師

    工作內容

    1. 客戶需求專案支援
    2. 觸控演算法開發與維護
    3. 觸控演算法數據分析

    職務類別

    演算法工程師




    白板題


    Q1-1. 不使用 size of 算出這個結構佔用記憶體大小

    struct ST
    {
        int I;
        float F;
    }AA;
    


    解答:

    struct ST
    {
        int I;
        float F;
    }AA;
    
    int mysize(struct ST* a)
    {
        return (char*)(a + 1) - (char*)a;
    }
    
    int main()
    {
        AA.I = 10;
        AA.F = 4.5;
        printf("Size of AA: %dn", mysize(&AA));
        return 0;
    }
    
    
    Terminal:
    Size of AA: 8
    

    可以這樣解的原因是結構變數指標+1 就是獲得這個結構變數後的記憶體位置,

    用 char* 算是因為 char 佔用 1 Byte。



    Q1-2. 用 C 實現 $ sum_{x=0}^{2} frac{2^{x}e^{x-1}}{32}$,不能使用 division,

                並把整數與浮點數部份分別存入 AA 結構。 

                        ($e^{-1}=0.367879, e^{0}=1, e=2.718281$)


    解答:

    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    
    struct ST
    {
        int I;
        float F;
    }AA;
    
    
    int main()
    {
        int size = 3;
        float *exp = malloc((size + 1) * sizeof(float));
        exp[0] = 0.367897;
        exp[1] = 1.0;
        exp[2] = 2.718281;
        exp[3] = 0.0;
        
        for(int x=0; x<size; x++)
        {
            float bx = 2.0;
            bx = pow(bx, (x-5));
            exp[size+1] = exp[size+1] + bx*exp[x]; 
        }
        
        AA.I = (int)exp[size+1];
        AA.F = exp[size+1] - AA.I;
        
        printf("Result int part: %d, float part: %f", AA.I, AA.F);
        
        return 0;
    }
    
    Terminal:
    Result int part: 0, float part: 0.413782
    

    除了那個 pow 的 MATH.h 不太確定能不能用外,

    其他東西還蠻直覺的。

    Q2.利用指標反轉陣列 array arr[7] ={1,2,3,4,5,6,7}


    解答:

    void swap(int *head, int *tail)
    {
        *head = (*head)^(*tail);
        *tail = (*head)^(*tail);
        *head = (*head)^(*tail);
    }
    
    
    int main()
    {
        int arr[7] = {1,2,3,4,5,6,7};
        int *head = &arr[0];
        int *tail = &arr[6];
        
        while(head<tail)
        {
            swap(head, tail);
            head++;
            tail--;
        }
        for (int i = 0; i < 7; i++)
        {
            printf("%d ", arr[i]);
        }
        
        return 0;
    }
    
    Terminal:
    7 6 5 4 3 2 1


    Q3. array arr={1,3,5,7,9,11,13,17} ,用二分搜尋法找出 11 的 index 為何?


    解答:

      
    int find(int* arr, int H, int T, int target)
    {
        if(H==T) {return H;}
        
        if (target < arr[(int)((H + T)/2)])
        {
            return find(arr, H, (int)((H + T)/2), target);
        }
        else
        {
            return find(arr, (int)((H + T)/2)+1, T, target);
        }
    }
    
    
    int main()
    {
        int arr[8] = {1,3,5,7,9,11,13,17};
        int target = 11;
        int res = find(arr, 0, 7, 11);
        printf("Index of 11: %d", res);
        return 0;
    }
    
    Terminal:
    Index of 11: 6
    

    Q4.下面這段函式定義是否合法? 

    Q5.為什麼它合法或不合法?

      
    int* fun(int *x){return &x*&x;}
    float* fun(float *x){return &x*&x;}
    
    int main()
    {
        int x1 = 4;
        float x2 = 4.5;
        int* result1 = fun(x1);
        float* result2 = fun(x2);
        return 0;
    }
    

    解答:

    Q4 不合法

    Q5 主要有兩個原因:

            第一個是沒給編譯器定義,這關係到函式多載(Function Overloading),

            在 C 裡面就不允許函式多載、不同 IO 共用同一個函式名稱,

            在 CPP 的話就允許,但是這邊沒說到用 gcc or g++ 編譯。


            第二個是指標觀念,當函式傳入 *x,則函式內的 &x 等價 **x。

            雙重指標 **x 沒有辦法直接做乘法運算。

    心得

    現場我有把 Q2, Q3 解出,

    Q1-1肯定是我自己寫錯,Q1-2 第一眼看成積分直接空白,回來越想越不對。

    Q4, Q5 可能是我 CPP 寫太多直覺以為只考函式多載,就騰了合法兩字送出,

    還大膽解釋了一下。

    沒想到裡面還有藏指標(汗。

    以上都是英文出題,但是我沒記好英文全文,所以就憑自己印象寫出來,

    如有筆誤歡迎來信指正。

    這次是 104 主動投遞,人資主動打來約時間,

    我也問了在職的話能不能一面線上?

    結果是要到新竹現場考試,也通知筆試沒過就不面試。

    行前還要我準備碩論跟部落格的報告。

    這次整個面試流程大概是我提早半個小時到瑞鼎,

    櫃台帶我去一樓小房間,

    約定時間快到時,一位看起來像是人資(?)的人進來發考卷說寫半小時。

    考完後人資將考卷拿給主管批改,過大概二十分鐘下來說我沒過資格,

    還問我會不會出去?

    OS:這不廢話嗎?不然你以為我怎麼出現在這裡?

             嚴重懷疑你有特異功能看得到我的隱形直昇機或是隱形任意門。

    雖然回家想想自己解得不甚理想,但是從頭到尾都沒見到主管這點讓我有點火大。

    準備了一堆東西連主管都沒見著,幹麻不讓我線上開筆記本實況寫程式就好了?

    以上。

  • Quick Sort 利用快速排序法解決重複元素排序

     

    快速排序算法擁有最佳計算複雜度 O(n log n),

    但在特殊情況下會退化成 O(n^2)。

    其中一個特殊情況就是大量重複元素的排列問題。

    解決問題前,

    先介紹快速排序法的實現。

    假設你得到一隨機一維陣列要做升冪排序,

    最終的結果需要是這樣:

    選定隨機陣列最左側為 pivot ,pivot  為 L指標、最右側為 R指標。

    首先先判斷終止條件:R 的 index 是否等於 L 的 index?

    等於的話就將 pivot 的 值 與 R、L 的值交換,進行下一循環。

    R 的 index 不等於 L 的 index的話繼續尋找:

     R指標啟動尋找小於等於 pivot 的值,L 尋找大於 pivot 的值,找到就鎖定。

        若 R 的 index != L 的 index,則兩者的值交換。

    以此類推,當 R 的 index == L 的 index 時,交換 pivot 與 R 的值。
    並以交換點為準,分割左邊右邊兩個子循環。

    這邊可以順便推導為什麼 quick sort 的計算複雜度為 O(n log n),

    因為相較於 bubble sort 中每個 pivot  要比較 n-1 個元素,

    quick sort 在第二次以後的理想狀況每個 pivot 只需比較 (n/2)^m個元素
    (m=排序次數-1)。

    所以最佳計算複雜度才會是 O(n log n)  // n個 pivot 乘上該次比較元素量。

    但是齁,人算不如天算。

    有時候就會遇到很棘手的情況,讓 quick sort  一點都不 quick。

    主要分為兩種:

    1. 已排序數列。
    2. 存在著大量重複元素的數列。

        1.已排序數列。

    已排序數列的問題在於分割的左右子陣列不平衡,
    導致需要搜尋的元素趨近於 n。
    如此一來計算複雜度便會退化成 O(n^2)
    解法:把數列打亂即可解決這個問題。




        2.存在著大量重複元素的數列。

    這個就比較麻煩了,因為打亂也解決不了(#。
    這個問題在於要搜尋相等於 pivot 的重複數,
    兩數之間總共有三種關係嘛:大於、等於、小於。
    之前的方法一直把等於的方法掛在 L指標上面做,
    解決問題的核心精神是特別考慮等於的情況處理。

    重複數處理範例:


    令 pivot 為最左側的 index,L的 index 等於 privot +1,R 的 index 為最右側的 index。

    一樣先判斷 L 有沒有大於 R?

    沒有的話 L 先搜尋,分為三種情況:


    1.L中的元素比 privot 還要小:
        L 與 pivot 交換元素。
        
    privot++
        L++

    2.L中的元素等於 privot :
         L++


    3.L中的元素大於 privot :
         檢查 R元素
            R元素 大於  pivot:
                R–
            R元素 等於  pivot:
                L元素 與 R元素交換
                R–
                L++
            R元素 小於  pivot:
                L元素 與 R元素交換
                R–
                L元素 與 
     pivot交換
                privot++
                L++


    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include <iostream<
    #include <vector<
    #include <algorithm<
    #include <random<
    
    void swap(int *array, int i, int j) {
        
        array[i] = array[i] ^ array[j];
        array[j] = array[i] ^ array[j];
        array[i] = array[i] ^ array[j];
    }
    
    void quicksort(int *array, int left, int right) {
            if (left >= right) 
            {
                return;
            }
            int P = left;
            int L = left;
            int R = right;
    
            while(L <= R)
            {
                if(nums[L] < nums[P])
                {
                    swap(nums, L, P);
                    L++;
                    P++;
                }
                else if(nums[L] == nums[P])
                {
                    L++;
                }
                else
                {
                    if(nums[R] > nums[P])
                    {
                        R--;
                    }
                    else if(nums[R] == nums[P])
                    {
                        swap(nums, R, L);
                        swap(nums, L, P);
                        R--;
                        L++;
                    }
                    else
                    {
                        swap(nums, R, L);
                        swap(nums, L, P);
                        R--;
                        L++;
                        P++;
                    }
                }
            }
            quicksort(nums, left, P-1);
            quicksort(nums, P+1, right);
    }
    
    int main() {
        int ARRAY_SIZE = 100;
        int array[ARRAY_SIZE];
    
        // 設定隨機種子
        srand(time(0));
    
        // 生成隨機一維陣列
        for (int i = 0; i < ARRAY_SIZE; i++) {
            array[i] = rand() % 11; // 生成 0 到 100 之間的隨機整數
        }
    
        printf("原始陣列:");
        for (int j = 0; j < ARRAY_SIZE; j++) {
            printf("%d ", array[j]);
        }
        printf("n");
    
        quicksort(array, 0, ARRAY_SIZE - 1);
    
        printf("排序後:");
        for (int j = 0; j < ARRAY_SIZE; j++) {
            printf("%d ", array[j]);
        }
        printf("n");
    
        return 0;
    }
    
    
    

    好,大功告成!

    你現在可以用 Quick Sort 面對多重複元素的排列問題了!