Skip to content

Instantly share code, notes, and snippets.

@deysumitkr
Last active December 30, 2017 06:34
Show Gist options
  • Save deysumitkr/38aff21d52ee96825ceae29c0d969800 to your computer and use it in GitHub Desktop.
Save deysumitkr/38aff21d52ee96825ceae29c0d969800 to your computer and use it in GitHub Desktop.
Mark points, lines, boxes (rectangles) or polygons on Images - (OpenCV required) [Example at bottom]
//
// Created by sumit on 20/9/17.
//
#include "Annotate.h"
// ----------- draw --------------
template <> void Annotate<cv::Point_<int>>::draw(cv::Mat &showImage) {
for(int i=0; i<mArray.size(); i++)
cv::circle(showImage, mArray[i], 3, cv::Scalar(0, 250, 0), -1);
}
template <> void Annotate<cv::Point_<float>>::draw(cv::Mat &showImage) {
for(int i=0; i<mArray.size(); i++)
cv::circle(showImage, mArray[i], 3, cv::Scalar(0, 250, 0), -1);
}
template <> void Annotate<line>::draw(cv::Mat &showImage) {
for(int i=0; i<mArray.size(); i++)
cv::line(showImage, mArray[i].p1, mArray[i].p2, cv::Scalar(0, 250, 0), 2);
}
template <> void Annotate<cv::Rect>::draw(cv::Mat &showImage) {
for(int i=0; i<mArray.size(); i++)
cv::rectangle(showImage, mArray[i], cv::Scalar(0, 250, 0), 2);
}
template <> void Annotate<Polygon>::draw(cv::Mat &showImage) {
if(mArray.empty()) return;
for (int i=0; i<mArray.size(); i++) {
Polygon p = mArray[i];
p.draw(showImage);
}
}
// ---------------- window name ------------------
template <> std::string Annotate<cv::Point_<int>>::windowName(int numberOfItems, std::string remark) {
std::ostringstream ss;
if(numberOfItems > 0) ss << "UI | Click " << numberOfItems << " Points | " + remark;
else ss << "UI | Draw Points | " + remark;
cv::namedWindow(ss.str());
cv::setMouseCallback(ss.str(), callbacks::points, this);
return ss.str();
}
template <> std::string Annotate<cv::Point_<float>>::windowName(int numberOfItems, std::string remark) {
std::ostringstream ss;
if(numberOfItems > 0) ss << "UI | Click " << numberOfItems << " Points | " + remark;
else ss << "UI | Draw Points | " + remark;
cv::namedWindow(ss.str());
cv::setMouseCallback(ss.str(), callbacks::points2f, this);
return ss.str();
}
template <> std::string Annotate<line>::windowName(int numberOfItems, std::string remark) {
std::ostringstream ss;
if(numberOfItems > 0) ss << "UI | Draw " << numberOfItems << " Lines | " + remark;
else ss << "UI | Draw Lines | " + remark;
cv::namedWindow(ss.str());
cv::setMouseCallback(ss.str(), callbacks::lines, this);
return ss.str();
}
template <> std::string Annotate<cv::Rect>::windowName(int numberOfItems, std::string remark) {
std::ostringstream ss;
if(numberOfItems > 0) ss << "UI | Draw " << numberOfItems << " Boxes | " + remark;
else ss << "UI | Draw Boxes | " + remark;
cv::namedWindow(ss.str());
cv::setMouseCallback(ss.str(), callbacks::boxes, this);
return ss.str();
}
template <> std::string Annotate<Polygon>::windowName(int numberOfItems, std::string remark) {
std::ostringstream ss;
if(numberOfItems > 0) ss << "UI | Draw " << numberOfItems << " Polygons | " + remark;
else ss << "UI | Draw Polygons | " + remark;
cv::namedWindow(ss.str());
cv::setMouseCallback(ss.str(), callbacks::Polygons, this);
return ss.str();
}
//------------------ CallBacks -------------------
void callbacks::points(int event, int x, int y, int flags, void *param) {
Annotate<cv::Point_<int>>* ui = (Annotate<cv::Point_<int>>*)param;
switch(event){
case cv::EVENT_LBUTTONUP: ui->addItem(cv::Point(x,y)); break;
case cv::EVENT_RBUTTONUP: ui->removeLastItem(); break;
case cv::EVENT_MBUTTONUP: ui->getArray()->clear(); break;
default:;
}
}
void callbacks::points2f(int event, int x, int y, int flags, void *param) {
Annotate<cv::Point_<float>>* ui = (Annotate<cv::Point_<float>>*)param;
switch(event){
case cv::EVENT_LBUTTONUP: ui->addItem(cv::Point(x,y)); break;
case cv::EVENT_RBUTTONUP: ui->removeLastItem(); break;
case cv::EVENT_MBUTTONUP: ui->getArray()->clear(); break;
default:;
}
}
void callbacks::lines(int event, int x, int y, int flags, void *param) {
Annotate<line>* ui = (Annotate<line>*)param;
static bool drag = false;
switch(event){
case cv::EVENT_LBUTTONDOWN: if(!drag) { line l; l.p1 = cv::Point(x, y); l.p2 = cv::Point(x,y); ui->addItem(l); drag = true; } break;
case cv::EVENT_LBUTTONUP: if(drag) { drag = false; } break;
case cv::EVENT_MOUSEMOVE: if(drag && !ui->getArray()->empty()) { ui->getArray()->back().p2 = cv::Point(x,y); } break;
case cv::EVENT_RBUTTONUP: { ui->removeLastItem(); drag = false;}
case cv::EVENT_MBUTTONUP: ui->getArray()->clear(); break;
default:;
}
}
void callbacks::boxes(int event, int x, int y, int flags, void *param) {
Annotate<cv::Rect>* ui = (Annotate<cv::Rect>*)param;
static cv::Rect r;
static bool drag = false;
switch(event){
case cv::EVENT_LBUTTONDOWN: if(!drag) { r = cv::Rect(cv::Point(x,y), cv::Point(x,y)); ui->addItem(r); drag = true; } break;
case cv::EVENT_LBUTTONUP: if(drag) { drag = false; } break;
case cv::EVENT_MOUSEMOVE: if(drag && !ui->getArray()->empty()) { ui->getArray()->back() = cv::Rect(r.tl(), cv::Point(x,y)); } break;
case cv::EVENT_RBUTTONUP: { ui->removeLastItem(); drag = false;} break;
case cv::EVENT_MBUTTONUP: ui->getArray()->clear(); break;
default:;
}
}
void callbacks::Polygons(int event, int x, int y, int flags, void *param) {
Annotate<Polygon>* ui = (Annotate<Polygon>*)param;
static bool drag = false;
switch(event){
case cv::EVENT_LBUTTONUP:
if(!drag) {
Polygon poly;
poly.addVertex(cv::Point2f(x,y)); poly.addVertex(cv::Point2f(x,y));
ui->addItem(poly);
if((ui->getArray()->back().getMaxVertices() > 0) && (ui->getArray()->back().getMaxVertices() > ui->getArray()->back().getPolygonVertices()->size())){ // delete oldest vertex on overflow
ui->getArray()->back().getPolygonVertices()->erase(ui->getArray()->back().getPolygonVertices()->begin());
}
drag = true;
}
else {
Polygon* poly = &ui->getArray()->back();
poly->getPolygonVertices()->back() = cv::Point2f(x,y);
poly->addVertex(cv::Point2f(x,y));
if((ui->getArray()->back().getMaxVertices() > 0) && (ui->getArray()->back().getMaxVertices() > ui->getArray()->back().getPolygonVertices()->size())){ // delete oldest vertex on overflow
ui->getArray()->back().getPolygonVertices()->erase(ui->getArray()->back().getPolygonVertices()->begin());
}
}
break;
case cv::EVENT_MOUSEMOVE:
if(drag && !ui->getArray()->empty()) {
Polygon* poly = &ui->getArray()->back();
poly->getPolygonVertices()->back() = cv::Point2f(x,y);
} break;
case cv::EVENT_RBUTTONUP:
if(drag) { drag = false; }
if(!ui->getArray()->empty()) {
Polygon *poly = &ui->getArray()->back();
poly->removeLastVertex();
if(poly->getPolygonVertices()->size() < 2) { ui->removeLastItem(); drag = false; }
} break;
case cv::EVENT_MBUTTONUP: { ui->getArray()->clear(); drag=false; } break;
default:;
}
if(!ui->getArray()->empty()) ui->getArray()->back().lastDrawingFinished = !drag;
}
//
// Created by sumit on 20/9/17.
//
#ifndef ANNOTATE_H
#define ANNOTATE_H
#include <iostream>
#include <opencv2/opencv.hpp>
/**
* @details
* left-click to place a point, right-click to remove last placed point/line, middle-click to clear everything and start fresh
* space to return the annotated points as vector
* esc to cancel and return empty vector
*/
namespace callbacks {
void points(int event, int x, int y, int flags, void* param);
void points2f(int event, int x, int y, int flags, void *param);
void lines(int event, int x, int y, int flags, void* param);
void boxes(int event, int x, int y, int flags, void* param);
void Polygons(int event, int x, int y, int flags, void *param);
}
struct line {
cv::Point2f p1, p2;
double m, c;
};
struct Polygon {
private:
int maxVertices = -1;
std::vector<cv::Point2f> points;
cv::Scalar color = cv::Scalar(0, 250, 0);
int lineThickness = 2;
public:
Polygon(){}
Polygon(const int vx) : maxVertices(vx) {}
bool lastDrawingFinished = true;
void setMaxVertices(const int x) { maxVertices = x; }
int getMaxVertices() const { return maxVertices; }
void addVertex(cv::Point2f p) { points.push_back(p); }
void removeLastVertex() { points.pop_back(); }
std::vector<cv::Point2f>* getPolygonVertices() { return &points; }
void draw(cv::Mat &im){
if(lastDrawingFinished) drawClosed(im);
else drawOpen(im);
}
void drawOpen(cv::Mat &im) {
if(points.size() > 1)
for (int i=0;i<points.size()-1;i++) {
cv::line(im, points[i], points[i+1], color, lineThickness);
}
}
void drawClosed(cv::Mat &im) {
if(points.size()>1){
drawOpen(im);
cv::line(im, points[0], points.back(), color, lineThickness);
}
}
};
template <typename T>
class Annotate {
private:
cv::Mat mImage;
std::vector<T> mArray;
unsigned int mFrameInterval=25;
public:
Annotate() {};
Annotate(cv::Mat im) : mImage(im) {}
void setImage(cv::Mat im) { mImage = im; }
void addItem(T item) { mArray.push_back(item); }
void removeLastItem() { if(!mArray.empty()) mArray.pop_back(); }
std::vector<T>* getArray() { return &mArray; }
std::vector<T> getItems(int numberOfItems, std::string remark=""){
std::string winName = windowName(numberOfItems, remark);
mArray.clear(); // start fresh
cv::Mat showImage;
bool loop = true;
while(loop){
showImage = mImage.clone();
if((numberOfItems > 0) && (mArray.size() > numberOfItems)) mArray.erase(mArray.begin()); // remove oldest element on overflow
draw(showImage);
cv::imshow(winName, showImage);
char key = cv::waitKey(mFrameInterval);
switch(key){
case 27: cv::destroyWindow(winName); return std::vector<T>(); // escape key
case 32: loop = false; break; // space key
default: ;
}
}
cv::destroyWindow(winName);
return mArray;
}
void getItems(int numberOfItems, std::vector<T> &outArray, std::string remark="") { outArray = getItems(numberOfItems, remark); }
void draw(cv::Mat&);
std::string windowName(int numberOfItems, std::string remark);
};
#endif //ANNOTATE_H
#include <iostream>
#include <opencv2/opencv.hpp>
#include "Annotate.h"
int main(){
cv::Mat im = cv::imread("path/to/an/image");
// --- Points ---
// left-click: add a point
// right-click: delete the last added point
// middle-click: delete all points
// space: close window and return vector of points
// esc: close window and return empty vector
Annotate<cv::Point2f> ui4Points(im);
// get max of 4 points from user - the string is appended to the window name
std::vector<cv::Point2f> pts = ui4Points.getItems(4, "Anti-clockwise from bottom-left");
// get unlimited number of points from user - the string is appended to the window name
std::vector<cv::Point2f> pts = ui4Points.getItems(0, "Anti-clockwise from bottom-left");
// --- Rectangles ---
// left-click drag: draw rectangle
// right-click: delete the last added rectangle
// middle-click: delete all rectangles
// space: close window and return vector of rectangles
// esc: close window and return empty vector
Annotate<cv::Rect> ui4Rects(im);
// get max of 6 rectangles drawn by user - the string is appended to the window name
std::vector<cv::Rect> boxes = ui4Rects.getItems(6, "Draw ROIs");
// --- Straight Lines ---
// left-click drag: draw line
// right-click: delete the last added line
// middle-click: delete all lines
// space: close window and return vector of lines
// esc: close window and return empty vector
Annotate<line> ui4Lines(im);
// get max of 3 lines drawn by user - the string is appended to the window name
std::vector<line> lines = ui4Lines.getItems(3, "Draw 3 Lines");
// --- Polygons ---
// left-click: add new vertex
// right-click: close polygon (if drawing) / delete the last added vertex
// middle-click: delete all polygons
// space: close window and return vector of polygons
// esc: close window and return empty vector
Annotate<Polygon> ui4Polygon(im);
// get max of 2 polygons drawn by user - the string is appended to the window name
std::vector<Polygon> polygons = ui4Polygon.getItems(2, "Draw 2 Polygons");
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment