Last active
December 30, 2017 06:34
-
-
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]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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