2021年2月26日 星期五

linux c++ 掃描按鍵, 透過 UDP 遙控裝置

#include <sys/time.h> // struct timeval
#include <termios.h>  // struct termios
#include <unistd.h>   // close
#include <stdio.h>    // printf
#include <string.h>   // memset
#include <stdlib.h>   // atoi
#include <netdb.h>    // gethostbyname
#include <arpa/inet.h>// inet_ntoa
#include <signal.h>  // signal

class DeltaPWM {
  private:
    unsigned char value, click;
  public:
    void reset() {// reset to default value
      click  = 0;
      value  = 1;
    }
    auto operator ()(int key = 0) {
      if (key > 0) {
        if (key != click) { // key changes
          value = 1;// begin from 1
          click = key;// keep to compare next time
        } else if (value < 10) value ++;// same key pressed
      }// saturat at 10, increase nonlinearly
      return value;
    };
    DeltaPWM() { reset(); }
};

int main(void) {
  const char *hostname = "myDecive.local";// remote target
  const int udpPort = 8888;// service port number
  signal(SIGINT, [](int sig) {
    printf("CTRL-C\n");  
  });

  printf("Waiting: %s...\n", hostname);
  struct hostent *host = gethostbyname(hostname);
  if (host == NULL) {
    printf("%s not known\n", hostname);
    return 1; // NS lookup fail, nothing to do
  }
  int udpFD = socket(AF_INET, SOCK_DGRAM, 0);
  if (udpFD < 0) return 2; // socket init fail, nothing to do
    
  printf("'q' or double click 'ESC' to break, Arrow keys to remote control.\n");
  struct sockaddr_in udpAddr; // allocate a struct sockaddr_in from stack
  memset(&udpAddr, 0, sizeof(udpAddr));// empty udpAddr
  udpAddr.sin_family = AF_INET;
  bcopy(host->h_addr_list[0], (caddr_t) &udpAddr.sin_addr, host->h_length);
  // udpAddr.sin_addr.s_addr = inet_addr(inet_ntoa(*(struct in_addr *) host->h_addr));
  udpAddr.sin_port = htons(udpPort);
  auto udpSend = [&](const char *msg) {
      int len = strlen(msg);
      if(len > 0) sendto(udpFD, msg, len, 0,
          (const struct sockaddr *) &udpAddr,
           sizeof(struct sockaddr_in)
      );              
  };
 
  static char keyBuffer[1024]; // share keyBuffer
  static auto parseKey = [](const char *key, int *value) -> bool {
    static char valBuffer[32];
    if (keyBuffer[0] == 0 || ! key) return false;// scan fail
    char *findKey = strstr(keyBuffer, key);
    if (findKey) {
      char *rewind = findKey;
      while(rewind > keyBuffer) {// check if not comment
        rewind --;
        char &ar = *rewind;
        if (ar == '\n' || ar == '\r' || ar == ',' || ar == '#') break;
      }
      if(*rewind != '#') { // discard comment
        findKey += strlen(key);
        if(*findKey == '=') findKey ++;// skip '=' if provide
        char *dest = valBuffer;
        for(int i = 0; i < 31; i ++) { // tupto 31 characters
          char &af = *findKey ++;
          if (af == 0 || af == '\n' || af == '\r' || af == ' ' || af == ',' || af == '#') break;
          *dest ++ = af;
        }
        *dest = 0;// EOS to split string
        *value = atoi(valBuffer);// convert to integer
        return true;// sucess
      }
    }
    *value = 0;  // value invalid
    return false;// scan fail
  };
      
  struct termios keepTerm;
  tcgetattr(STDIN_FILENO, &keepTerm);
  auto term = keepTerm;// duplicate
  term.c_lflag &= ~(ICANON | ECHO); // mask for ICANON + ECHO
  tcsetattr(STDIN_FILENO, TCSANOW, &term);// set a new configuration

  DeltaPWM dp, dz, dr;
  int power =  0;// initial power to send
  int yaw   = 16;// initial yaw to send
  int pitch =  0;// initial pitch to send
  int roll  =  0;// initial roll to send
  int maxFD  = udpFD + 1;
  char txtBuffer[1024];
  sprintf(txtBuffer, "power=%d,yaw=%d,pitch=%d,roll=%d", power, yaw, pitch, roll);
  udpSend(txtBuffer);

  while (true) {
    fd_set fdBits;
    FD_ZERO(&fdBits);// reset all bits
    FD_SET(STDIN_FILENO, &fdBits);
    FD_SET(udpFD, &fdBits);
    struct timeval timeout = {.tv_sec = 0, .tv_usec = 40000};// 40 mSec = 0.04 Sec
    int event = select(maxFD, &fdBits, NULL, NULL, &timeout);
    if (event < 0) break; // error event proceed first

    if(FD_ISSET(udpFD, &fdBits)) { // udp received event
      int len = recv(udpFD, keyBuffer, 1023, 0);
        if (len > 0) {
        keyBuffer[len] = 0;
        printf("###:%s\n", keyBuffer);
        int temp = 0;
        if(parseKey("power", &temp)) power = temp;
        if(parseKey("yaw"  , &temp))   yaw = temp;
        if(parseKey("pitch", &temp)) pitch = temp;
        if(parseKey("roll" , &temp))  roll = temp;
        printf("power:%d@%d, yaw:%4d, pitch:%4d@%4d, roll:%4d@%4d\n",
          power, dp(), yaw, pitch, dz(), roll, dr());
      }// udp packet received, update and show current status
    }

    if(FD_ISSET(STDIN_FILENO, &fdBits)) { // user input event
      int ch = getchar();// printf("KEY CODE = %d\n", ch);
      if (ch == 'q') break;// user break by pressing 'q'
      if (ch == 27) {
        ch = getchar();  // printf("ESC KEY= %d\n", ch);
        if(ch == 27) break;// user break by double ESC clicks
        if(ch == 91) {// keypad scan
          ch = getchar();// printf("ARROW= %d\n", ch);
          switch(ch) {
            case 65: // arrow up, to increase power
                power += dp(ch);
                sprintf(txtBuffer, "power=%d", power);
                break;
            case 66: // arrow down, to decrease power
                power -= dp(ch);
                sprintf(txtBuffer, "power=%d", power);
                break;
            case 67:// arrow right,
                yaw ++;
                sprintf(txtBuffer, "yaw=%d", yaw);
                break;
            case 68:// arrow left,
                yaw --;
                sprintf(txtBuffer, "yaw=%d", yaw);
                break;
            case 50: //Ins, to toward right
                roll += dr(ch);
                sprintf(txtBuffer, "roll=%d", roll);
                break;
            case 51: //Del, to toward left
                roll -= dr(ch);
                sprintf(txtBuffer, "roll=%d", roll);
                break;         
            case 53: // pageup to forward
                pitch += dz(ch);
                sprintf(txtBuffer, "pitch=%d", pitch);
                break;
            case 54: // pagedown to backward                  
                pitch -= dz(ch);
                sprintf(txtBuffer, "pitch=%d", pitch);
                break;
                
            case 70: // home
            case 72: // end
            default:
                txtBuffer[0] = 0;// clear txtBuffer
                printf("KEY: %d not defined\n", ch);
                break;
          }// keyCode validate
          if (strlen(txtBuffer) > 0) udpSend(txtBuffer);
        }// remote control
      }// keyCode scan
    }

    if (event == 0) {// timeout event finally
      dp.reset();
      dz.reset();
      dr.reset();
    }
  }// loop until error happen or user break

  udpSend("power0,yaw0,pitch0,roll0");// set all to zero before end
  close(udpFD);// close udp port
  tcsetattr(STDIN_FILENO, TCSANOW, &keepTerm);// restore termial
  return 0;// no error
}

2021年2月5日 星期五

Linux mint 上安裝 opencv c++ 開發環境, 並展示範例程式

先安裝好開發環境所需相關程式庫:

      sudo apt-get  install  libopencv-dev

opencv 文件參考: https://docs.opencv.org/master/d8/dfe/classcv_1_1VideoCapture.html ,儲存下列範例程式碼:

//example.cpp
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
int  main( ) {
    String title = "computerVision";
    printf("Press ESC or Ctrl-C to exit...\n");
    VideoCapture video;
    Mat srcImage;
    while (waitKey(200) != 27) {
        video.open("http://192.168.12.102/snap.jpg");// it will release first, than open
        if(video.isOpened( ) && video.read(srcImage)) {// read from file
            imshow(title, srcImage);
        }
    }
    video.release( );
}

編譯上述程式碼並執行:

      g++  example.cpp   `pkg-config --cflags --libs opencv4` && ./a.out

若是使用 vscode 編寫程式, 需在目錄內先建一個目錄  .vscode, 並於該目錄裡, 將下列內容存成 c_cpp_properties.json 檔案,  這樣 vscode 才能找到相關標頭檔

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/usr/include/**",
                "/usr/local/include/**"
            ]
        }
    ],
    "version": 4
}

基本繪圖(矩形, 直線, 圓形, 橢圓形)範例程式: 

// g++ draw.cpp `pkg-config --cflags --libs opencv4`
// draw.cpp
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <unistd.h>
using namespace cv;

int main(void) {
    int pixels = 512; // pixels in one dimension
    Size size2D(pixels, pixels);// 2 dimension pixels: pixels L*L pixels
    Mat figure2D(size2D, CV_8UC3);// 2 dimension figure
    double fx1 = pixels / 4;// one quarter
    double fx2 = fx1 * 2 ;// double quarter = half
    double fx3 = fx1 * 3 ;// triple quarter

    static int lineThick = 2;
    static int lineType  = FILLED;
    static Scalar whiteColor(255, 255, 255);// B, G, R
    static Scalar blueColor (255,   0,   0);
    static Scalar greenColor(0,   255,   0);
    static Scalar redColor  (0,     0, 255);

    auto drawLine = [&figure2D](Point p1, Point p2) {
        line(figure2D, p1, p2, greenColor, lineThick, lineType);
    };// a lambda to capture an image of Mat and draw a line

    auto drawCircle = [&figure2D](Point c, double r) {
        circle(figure2D, c, r, redColor, lineThick, lineType);
    };// a lambda to capture an image of Mat and draw a circle

    auto drawHalf = [&figure2D](Point c, Size r, double deg){
        ellipse(figure2D, c, r, deg, 0, 180, blueColor, lineThick, lineType);
    };// a lambda to capture an image of Mat and draw a half of ellipse

    auto drawRectangle = [&figure2D](Point p1, Point p2) {
        rectangle(figure2D, p1, p2, whiteColor, lineThick, lineType);
    };// a lambda to capture an image of Mat and draw a rectangle
   
    auto drawShow = [&figure2D]( ){
        static String name = "figure";
        imshow(name, figure2D);
        return name.c_str( );
    };// a lambda to capture an image of Mat and show

    Size radius(fx1, fx2);// size of 2D
    Point center(fx2, fx2);
    drawRectangle(Point(fx1, fx1), Point(fx3, fx3));
    int  key = 0;   
    for (int rotate = 0; rotate < 360; rotate += 45) {
        double theta = rotate * M_PI / 180;// degree to theta
        Point pth(fx2*(1 + cos(theta) / 2), fx2*(1 + sin(theta) / 2));
        drawCircle(pth, 20);
        drawLine(pth, center);
        drawHalf(center, radius, rotate);
        drawShow( );       
        key = waitKey(1000);
        if(key > 0) break; // any key to break
    }
    return key > 0 ? key : waitKey(0);
}

使用已訓練好的資料庫(xml 格式檔)偵測臉部及眼部, 參考資料: https://docs.opencv.org/3.4/db/d28/tutorial_cascade_classifier.html

// g++ a.c `pkg-config --cflags --libs opencv4`
#include "opencv2/objdetect.hpp"
#include <opencv2/imgproc.hpp>
#include <opencv2/core/types_c.h>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
using namespace std;
 
int  main( ) {
    String faceXMLPath = "/usr/share/opencv4/haarcascades/haarcascade_frontalface_default.xml";
    String eyeXMLPath  = "/usr/share/opencv4/haarcascades/haarcascade_eye.xml";
    CascadeClassifier faceFilter, eyeFilter;
    if (!faceFilter.load(faceXMLPath)){
        printf("%s not exist", faceXMLPath.c_str( ));
        return -1;
    }
    if (!eyeFilter.load(eyeXMLPath)){
        printf("%s not exist", eyeXMLPath.c_str( ));
        return -1;
    }

    //Scalar blueColor= Scalar(255,0  ,0);// B, G, R
    Scalar greenColor = Scalar(0  ,255,0);
    Scalar redColor   = Scalar(0  ,0  ,255);
    Mat colorFrame, grayFrame;
    VideoCapture video;
    String title = "computerVision";
    printf("Press ESC to exit...\n");

    while (waitKey(200) != 27) { // FPS = 1000/200 = 5
        video.open("http://192.168.12.102/snap.jpg");// release first, than open

        if(video.isOpened( ) && video.read(colorFrame)) {// read a jpg
            cvtColor(colorFrame, grayFrame, COLOR_BGR2GRAY);
            equalizeHist(grayFrame, grayFrame);

            vector<Rect> faces;
            faceFilter.detectMultiScale(grayFrame, faces);
            for (size_t i = 0; i < faces.size( ); i++) {
                auto ox = faces[i].x;
                auto oy = faces[i].y;
                Rect edgeFace = cvRect(ox, oy, faces[i].width, faces[i].height);
                rectangle(colorFrame, edgeFace, redColor);

                vector<Rect> eyes;
                Mat roi = grayFrame(faces[i]);
                eyeFilter.detectMultiScale(roi, eyes);
                for (size_t j = 0; j < eyes.size( ); j++)  { 
                    auto x = ox + eyes[j].x;
                    auto y = oy + eyes[j].y;
                    Rect edgeEye = cvRect(x, y, eyes[j].width, eyes[j].height);
                    rectangle(colorFrame, edgeEye, greenColor);
                }
            }

            imshow(title, colorFrame);
            // imshow(title + "-grayFrame", grayFrame);
        }
    }
    video.release( );
    return 0;
}

2021年2月3日 星期三

使用 opencv.js 作簡單的邊緣偵測

 

參考: https://docs.opencv.org/master/d3/de6/tutorial_js_houghlines.html

將以下內容存成 html 檔案,  先抓取 http://192.168.12.102/snap.jpg 檔案後, 再作影像處理範例:

 <html>
    <head><meta charset="utf-8"><title>OpenCV demo</title>
        <script async src="https://docs.opencv.org/3.4.0/opencv.js" onload="onOpenCvReady( );" type="text/javascript"></script>
    </head>
    <body>
        <div><canvas id="computerVision"></canvas></div>
        <script>                       
            async function onOpenCvReady() {
                let result = await fetch("http://192.168.12.102/snap.jpg");
                let jpgURL = URL.createObjectURL(await result.blob());
                let resolv = false;   
                let future = new Promise((ready, _) => resolv=ready);
                let img    = new Image();
                img.onload = () => {
                    resolv && resolv(img);
                    URL.revokeObjectURL(jpgURL);
                    img.remove();
                };
                img.src = jpgURL;
               
                let src = cv.imread(await future);// cv: computer vision
                let dst = new cv.Mat();// an instance of matrix
                cv.cvtColor(src, dst, cv.COLOR_RGBA2GRAY, 0);// color to gray, 轉成灰階
                cv.Canny(dst, dst, 50, 200, 3); // gray to edge, 過濾成邊際線
               
                let lines = new cv.Mat();
                cv.HoughLinesP(dst, lines, 1, Math.PI / 180, 2, 0, 0)// 從邊際線提取線段
                for (let i = 0; i < lines.rows; ++i) {
                    let startPoint = new cv.Point(lines.data32S[i * 4], lines.data32S[i * 4 + 1]);
                    let endPoint = new cv.Point(lines.data32S[i * 4 + 2], lines.data32S[i * 4 + 3]);
                    cv.line(src, startPoint, endPoint, [255, 0, 0, 255]);// 將線段畫在原圖上
                }
                cv.imshow('computerVision', src);// 秀出合成圖
                lines.delete();
                dst.delete();
                src.delete();
            }
        </script>
    </body>
</html>

使用瀏覽器開啟上述檔案就可, 每次都要上網抓 opencv.js 感覺很麻煩, 可以先抓下來存成 opencv.js, 將它放在與 html 檔案同一目錄下, 稍微修改檔案路徑:
        <script async src="opencv.js" onload="onOpenCvReady();" type="text/javascript"></script>



簡單 c 程式碼, 根據五行八卦相生相剋推斷吉凶

#include "stdio.h" // 五行: //               木2 //      水1           火3 //         金0     土4 // // 先天八卦 vs 五行 //                    ...