一个基于OCV的人肉选取特征点程序
基于OpenCV写了一个交互式获取图片上的人肉选取的特征,并保存到文件的小程序。
典型应用场景:当在一个精度不高的应用需求中,相机分辨率差或者变形严重,某些棋盘点通过代码检测不出,就可以通过手工选取的方式。
使用
- 通过滚轮来缩放图片显示
- 单击右键设置显示中心点
- 单击左键选取并记录点
- 'c'来取消上一次取点
- 'q'退出并保存数据
界面
代码
#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;
}
/************************* Stay hungry, Stay foolish. @willhua ************************/