一个基于OCV的人肉选取特征点程序

基于OpenCV写了一个交互式获取图片上的人肉选取的特征,并保存到文件的小程序。
典型应用场景:当在一个精度不高的应用需求中,相机分辨率差或者变形严重,某些棋盘点通过代码检测不出,就可以通过手工选取的方式。

使用

  • 通过滚轮来缩放图片显示
  • 单击右键设置显示中心点
  • 单击左键选取并记录点
  • 'c'来取消上一次取点
  • 'q'退出并保存数据

界面

F7avYn.png

代码

#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;
}
posted @ 2019-01-05 20:11  willhua  阅读(423)  评论(0编辑  收藏  举报