基於OpenCV寫了一個交互式獲取圖片上的人肉選取的特徵,並保存到文件的小程序。
典型應用場景:當在一個精度不高的應用需求中,相機分辨率差或者變形嚴重,某些棋盤點經過代碼檢測不出,就能夠經過手工選取的方式。ios
#include <opencv2/opencv.hpp> #include <iostream> #include <stdlib.h> /* use guide: 1):left button click to pick one point; 2):right button click to set as the center to display; 3):wheel to zoom the image 4):key 'c' to remove the last picked point 5):key 'q' to quit the program and save point 6):use the "default size scale" to adjust the display size */ using namespace cv; using namespace std; const bool using_fix_param = true; const float SCALE_STEP = 0.1; const string WIN_NAME = "Pick_Point"; string data_save_path; //數據保存路徑 string PIC_PATH; //圖片路徑 Mat srcImg; //原始圖片 Mat curImg; //當前顯示的圖片 Size srcImgSize; //原始圖片大小 Size winSize; //顯示窗口大小 float curScale; //當前的縮放比例 Point2i curShowCenter; //當前顯示的圖像相對於srcImg偏移的座標 Point2i showRange; //顯示的圖片範圍,與curShowCenter共同組成了圖片的顯示範圍 float minScale; //縮放的最小比例 vector<Point2f> choosePoints; //經過本程序選取的點 void showdata() { if (false) { cout << ">>>>>>>>>>>>>>>>>>>>>>>>\n"; cout << "curScale:" << curScale << endl; cout << "curShowCenter:" << curShowCenter.x << "*" << curShowCenter.y << endl; cout << "showRange:" << showRange.x << "*" << showRange.y << endl; cout << "winSize:" << winSize.width << "*" << winSize.height << endl; cout << "<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"; } } //define the save-format for yourself during this function void saveAndQuit(vector<Point2f> pts) { const int POINT_PER_LINE = 4; FILE *stream = fopen(data_save_path.c_str(), "w"); for (int i = 0; i < pts.size(); ) { stringstream ss; for (int j = 0; j < POINT_PER_LINE && i < pts.size(); ++j, ++i) { //ss << pts[j].x << ", " << pts[j].y << ", "; ss << "Point2f(" << pts[i].x << ", " << pts[i].y << "), "; } ss << "\n"; string msg = ss.str(); if (i == pts.size()) { msg = msg.substr(0, msg.length() - 3); } fwrite(msg.c_str(), msg.length(), 1, stream); cout << msg << endl; } fflush(stream); fclose(stream); } void keepCenterValid() { int minCenterX = winSize.width / curScale / 2; int minCenterY = winSize.height / curScale / 2; int maxCenterX = srcImgSize.width - minCenterX; int maxCenterY = srcImgSize.height - minCenterY; if (curShowCenter.x < minCenterX) curShowCenter.x = minCenterX; if (curShowCenter.x > maxCenterX) curShowCenter.x = maxCenterX; if (curShowCenter.y < minCenterY) curShowCenter.y = minCenterY; if (curShowCenter.y > maxCenterY) curShowCenter.y = maxCenterY; } void showimg() { showdata(); Mat pts = srcImg.clone(); drawChessboardCorners(pts, Size(11, 11), choosePoints, false); //size能夠隨便寫,只要後面爲false便可 int left = curShowCenter.x - showRange.x / 2; if (left < 0) left = 0; int right = curShowCenter.x + showRange.x / 2; if (right > pts.cols) right = pts.cols; int top = curShowCenter.y - showRange.y / 2; if (top < 0) top = 0; int bottom = curShowCenter.y + showRange.y / 2; if (bottom > pts.rows) bottom = pts.rows; curImg = pts.colRange(left, right).rowRange(top, bottom).clone(); //cout << curImg.cols << " " << showRange.x << " " << curImg.rows << " " << showRange.y << endl; Mat scale_img; resize(curImg, scale_img, winSize); imshow(WIN_NAME, scale_img); void removeLastPoint(); int key = waitKey(); if (key == 'c') { removeLastPoint(); } else if(key == 'q'){ saveAndQuit(choosePoints); cout << "save and quit\n"; exit(0); } } void addPoint(int x, int y) { //使用這個能夠保證計算精度,直接使用curScale可能因爲前面計算的取整問題而致使精度問題,對於原圖尺寸較大且放大倍數也大時,這個問題會變的比較明顯 const float scaleX = winSize.width / (float)curImg.cols; const float scaley = winSize.height / (float)curImg.rows; //cout << "add point scale " << curImg.cols << " " << scaleX << " " << scaley << " " << curScale << endl; //使用這種方式,(curShowCenter.x - showRange.x / 2),能夠保持和imshow的時候一致,避免出現精度問題 float picx = (curShowCenter.x - showRange.x / 2) + x / scaleX; float picy = (curShowCenter.y - showRange.y / 2) + y / scaley; choosePoints.push_back(Point2f(picx, picy)); cout << ">>>>add:" << picx << " " << picy << endl; showimg(); } void removeLastPoint() { if (choosePoints.size() > 0) { choosePoints.erase(choosePoints.end() - 1); cout << "remove\n"; showimg(); } } void setShowCenter(int x, int y) { curShowCenter.x += (x - winSize.width / 2) / curScale; curShowCenter.y += (y - winSize.height / 2) / curScale; keepCenterValid(); showimg(); } void on_whellScaleEvent(int flags) { //返回值爲120的倍數。120表示滾動了一格。大於0表示向前,小於0表示向後 int v = getMouseWheelDelta(flags) / 120; showdata(); curScale += (v * SCALE_STEP); if (curScale < minScale) curScale = minScale; showRange.x = winSize.width / curScale; showRange.y = winSize.height / curScale; showimg(); } void on_mouse(int event, int x, int y, int flags, void* userdata) { switch (event) { case CV_EVENT_RBUTTONDOWN: //右鍵,設定顯示中心 setShowCenter(x, y); break; case CV_EVENT_LBUTTONDOWN: //左鍵單擊,選取點 addPoint(x, y); break; case CV_EVENT_MOUSEWHEEL: on_whellScaleEvent(flags); break; default: break; } } int main() { if (using_fix_param) { curScale = 0.3; } else { cout << "please input the default size scale:"; cin >> curScale; } minScale = curScale; //以用戶輸入的scale爲最小值,這個是恰好最小的倍數能填滿整個win if (using_fix_param) { PIC_PATH = "sample.jpg"; } else { cout << "\npicture path:"; cin >> PIC_PATH; } if (using_fix_param) { data_save_path = "data.txt"; } else { cout << "\ndata save path:"; cin >> data_save_path; } srcImg = imread(PIC_PATH, 1); srcImgSize = srcImg.size(); curShowCenter = srcImgSize / 2; winSize = Size(srcImgSize.width * curScale, srcImgSize.height * curScale); showRange.x = winSize.width / curScale; showRange.y = winSize.height / curScale; namedWindow(WIN_NAME); setMouseCallback(WIN_NAME, on_mouse); showimg(); waitKey(); return 0; }