#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月26日 星期五
linux c++ 掃描按鍵, 透過 UDP 遙控裝置
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>
Linux mint 玩 waydroid 一些心得
1. 目前使用 linux mint 22.1 作業系統可以順利跑起來, 可上官網去下載, 並安裝到硬碟. 2. 安裝 waydroid 可上網站 https://docs.waydro.id 參考看看: https://docs.waydro.id/usage/inst...
-
1. 上 Firebase 網站 註冊啟用 Firebase : https://firebase.google.com/ 2. 進入 Firebase Console 註冊開啟新專案 : https://console.firebase.google.com/...
-
Flutter 讓人很容易短時間就能開發出 app, 關鍵在於他開發了很多種小部件稱為 Widget, 將Widget 組合起來就是一個 app. 所謂 部 件(Widget)就是一個可以呈現在螢幕上的視覺系 物 件,幾乎可以說 Flutter 每個物件都是 部 件. 開發者透...