2017年10月4日 星期三

C / C++ 影像處理如何畫線與畫箭頭

C++ 影像處理如何畫線與畫箭頭

代碼關鍵函式要用點斜式,然後用for迴圈去跑x軸或y軸,跑完那一條線
struct ImgRaw{
    int width;
    int height;
    vector<unsigned char> raw_img;
};

void drawLine_p(ImgRaw& img, int y, int x, int y2, int x2, float val) {
    // 兩點之間的距離差
    float dx = static_cast<float>(x2-x);
    float dy = static_cast<float>(y2-y);
    // 以Y軸為主
    float sita=fastAtan2f(dy, dx);
    if (sita>45 and sita<135 or sita>225 and sita<315) {
        float slopeY = dx/dy; // 斜率
        for (int i = 0; i < abs(dy); i++) {
            int iFix = dy>0? i:-i;
            int currPos = static_cast<int>(iFix*slopeY+.5f + x);

            int distX = currPos;
            int distY = y+iFix;

            if (distX<0 or distX>=(int)img.width or distY<0 or distY>=(int)img.height) {
                return;
            }
            img.raw_img[distY*img.width + distX] = static_cast<unsigned char>(val);
        }
    } 
    // 以X軸為主
    else {
        float slopeX = dy/dx; // 斜率
        for (int i = 0; i < abs(dx); i++) {
            int iFix = dx>0? i:-i;
            int currPos = static_cast<int>(iFix*slopeX+.5 + y);

            int distX = x+iFix;
            int distY = currPos;

            if (distX<0 or distX>=(int)img.width or distY<0 or distY>=(int)img.height) {
                return;
            }
            img.raw_img[distY*img.width + distX] = (unsigned char)val;
        }
    }
}
然後角度的不一樣比如說45~90度跑Y軸會比較好,線連得比較密,如果是0~45度則是跑X軸比較好,才不會出現斷線。
角度的判斷計算使用。
float sita = fastAtan2f(dy, dx);
  • ImgRaw帶有圖像資訊、長、寬
  • fastAtan2f是從OpenCV複製出來的可以參考站內文這裡
接下來有了直線之後就是寫一個,輸入角度與長度的函式,簡單來說就是算出頭尾的位置然後帶入剛剛的函式畫線。
void drawLineRGB_s(ImgRaw& img, int y, int x, float line_len, float sg) {
        // 檢查
        if (line_len <= 0) { return; }
        // 算頭尾
        int x2 = x + line_len*cos(sg * M_PI/180);
        int y2 = y + line_len*sin(sg * M_PI/180);
        // 畫線
        drawLineRGB_p(img, y, x, y2, x2);
    }
再來箭頭就只是多話2條線,帶入剛剛的給點給角度的函式畫出箭頭
void draw_arrowRGB(ImgRaw& img, int y, int x, float line_len, float sg) {
        // 檢查
        if (line_len <= 0) { return; }
        // 算頭尾
        int x2 = x + line_len*cos(sg * M_PI/180);
        int y2 = y + line_len*sin(sg * M_PI/180);
        // 畫線
        drawLineRGB_p(img, y, x, y2, x2);
        // 畫頭
        drawLineRGB_s(img, y2, x2, 10, sg-150);
        drawLineRGB_s(img, y2, x2, 10, sg+150);
    }


如何使用

drawLine有兩個,一個是給定兩個點,把他們連起來,另一個是給定原點, 長度, 角度,也就是給極值的方式畫線。
    ImgRaw img_lineRGB(1280, 720);
    for (size_t i = 0; i < 36; i++) {
        Draw::draw_arrowRGB(img_lineRGB, 200, 200, 100.f*sqrt(2), i*10);
    } img_lineRGB.bmp("lineRGB.bmp");
ImgRaw 做的事情很簡單只是開一個 vector(1280*720*3) 然後儲存他的長1280和寬720
畫一圈的箭頭

原理

主要是利用斜率去算的,假設有兩個點,分別是 (0, 0) 與 (40,50) 斜率的公式,其中b是偏移量暫時不管她。
y=ax+b;
斜率就只是相減而已
dy=50-0;
dx=40-0;

m=dy/dx;
既然已知斜率那就用for迴圈去跑x的範圍 0~40 就可以得出y的位置了
不過這會遇到一個問題是當直線是上下的時候 for 迴圈跑的次數是0畫不出線,其次可以發現這種畫法當線條比較偏上的時候,45~135度的範圍會明顯斷線的感覺,因此這時候要變成以y為主,for迴圈去跑y反向計算出x。
這樣就可以畫出上圖比較好看一些的線條了。


範例程式

完整的程式參考:https://goo.gl/rAJLjH

沒有留言:

張貼留言