2018-12-14 17:04:42 +01:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2018 dimercur
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "img.h"
|
|
|
|
|
2019-01-15 16:25:48 +01:00
|
|
|
/**
|
|
|
|
* Tell if arena is empty (not found) or not
|
|
|
|
* @return true if no arena found, false otherwise
|
|
|
|
*/
|
2019-01-04 16:55:48 +01:00
|
|
|
bool Arena::IsEmpty() {
|
2019-01-09 16:32:16 +01:00
|
|
|
if ((this->arena.height == 0) || (this->arena.width == 0)) return true;
|
2018-12-14 17:04:42 +01:00
|
|
|
else return false;
|
|
|
|
}
|
|
|
|
|
2019-01-15 16:25:48 +01:00
|
|
|
/**
|
|
|
|
* Create new Img object based on image data
|
|
|
|
*
|
|
|
|
* @param imgMatrice Image data to be stored (raw data)
|
|
|
|
*/
|
2018-12-14 17:04:42 +01:00
|
|
|
Img::Img(ImageMat imgMatrice) {
|
|
|
|
this->img = imgMatrice.clone();
|
|
|
|
}
|
|
|
|
|
2019-01-15 16:25:48 +01:00
|
|
|
/**
|
|
|
|
* Convert object to a string representation
|
|
|
|
*
|
|
|
|
* @return String containing information on contained image (size and number of channel)
|
|
|
|
*/
|
2018-12-19 09:15:42 +01:00
|
|
|
string Img::ToString() {
|
2019-01-09 16:32:16 +01:00
|
|
|
return "Image size: " + to_string(this->img.cols) + "x" + to_string(this->img.rows) + " (dim=" + to_string(this->img.dims) + ")";
|
2018-12-19 09:15:42 +01:00
|
|
|
}
|
2018-12-21 16:36:52 +01:00
|
|
|
|
2019-01-15 16:25:48 +01:00
|
|
|
/**
|
|
|
|
* Create a copy of current object
|
|
|
|
*
|
|
|
|
* @return New Img object, copy of current
|
|
|
|
*/
|
2018-12-19 09:15:42 +01:00
|
|
|
Img* Img::Copy() {
|
|
|
|
return new Img(this->img);
|
|
|
|
}
|
2019-01-09 16:32:16 +01:00
|
|
|
|
2019-01-15 16:25:48 +01:00
|
|
|
/**
|
|
|
|
* Function for computing angle
|
|
|
|
* @param robots Position of robot
|
|
|
|
* @return Angle
|
|
|
|
*/
|
2019-01-04 16:55:48 +01:00
|
|
|
float Img::CalculAngle(Position robot) {
|
2018-12-14 17:04:42 +01:00
|
|
|
float a = robot.direction.x - robot.center.x;
|
2019-01-09 16:32:16 +01:00
|
|
|
float b = robot.direction.y - robot.center.y;
|
|
|
|
float angle = atan2(b, a);
|
|
|
|
return angle * 180.f / M_PI;
|
2018-12-14 17:04:42 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 16:25:48 +01:00
|
|
|
/**
|
|
|
|
* Function for computing angle
|
|
|
|
* @param pt1 ???
|
|
|
|
* @param pt2 ???
|
|
|
|
* @return Angle
|
|
|
|
*/
|
2019-01-04 16:55:48 +01:00
|
|
|
float Img::CalculAngle2(cv::Point2f pt1, cv::Point2f pt2) {
|
2018-12-14 17:04:42 +01:00
|
|
|
float a = pt1.x - pt2.x;
|
2019-01-09 16:32:16 +01:00
|
|
|
float b = pt1.y - pt2.y;
|
|
|
|
float angle = atan2(b, a);
|
|
|
|
return angle * 180.f / M_PI;
|
2018-12-14 17:04:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef __WITH_ARUCO__
|
2019-01-09 16:32:16 +01:00
|
|
|
|
2019-01-15 16:25:48 +01:00
|
|
|
/**
|
|
|
|
* Find center point of given aruco
|
|
|
|
* @param aruco Aruco coordinates
|
|
|
|
* @return Center point coordinate
|
|
|
|
*/
|
2019-01-09 16:32:16 +01:00
|
|
|
cv::Point2f Img::FindArucoCenter(std::vector<cv::Point2f> aruco) {
|
|
|
|
return ((aruco[0] + aruco[2]) / 2);
|
2018-12-14 17:04:42 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 16:25:48 +01:00
|
|
|
/**
|
|
|
|
* Find direction of given aruco
|
|
|
|
* @param aruco Aruco coordinates
|
|
|
|
* @return Orientation of aruco
|
|
|
|
*/
|
2019-01-09 16:32:16 +01:00
|
|
|
cv::Point2f Img::FindArucoDirection(std::vector<cv::Point2f> aruco) {
|
|
|
|
return ((aruco[0] + aruco[1]) / 2);
|
2018-12-14 17:04:42 +01:00
|
|
|
}
|
2019-01-15 16:25:48 +01:00
|
|
|
#endif // __WITH_ARUCO__
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used for computing distance
|
|
|
|
* @param p ???
|
|
|
|
* @param q ???
|
|
|
|
* @return Distance
|
|
|
|
*/
|
|
|
|
float Img::EuclideanDistance(cv::Point2f p, cv::Point2f q) {
|
|
|
|
cv::Point diff = p - q;
|
|
|
|
return cv::sqrt(diff.x * diff.x + diff.y * diff.y);
|
|
|
|
}
|
2018-12-14 17:04:42 +01:00
|
|
|
|
2019-01-15 16:25:48 +01:00
|
|
|
/**
|
|
|
|
* Compress current image to JPEG
|
|
|
|
* @return Image compressed as JPEG
|
|
|
|
*/
|
|
|
|
Jpg Img::ToJpg() {
|
|
|
|
Jpg imgJpg;
|
|
|
|
cv::imencode(".jpg", this->img, imgJpg);
|
|
|
|
return imgJpg;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Search available robots in an image
|
|
|
|
* @param arena Arena position for cropping image
|
|
|
|
* @return list of position, empty if no robot found
|
|
|
|
*/
|
|
|
|
std::list<Position> Img::SearchRobot(Arena arena) {
|
|
|
|
#ifdef __WITH_ARUCO__
|
2018-12-14 17:04:42 +01:00
|
|
|
ImageMat imgTraitment;
|
|
|
|
std::list<Position> positionList;
|
|
|
|
cv::Point2f areneCoor;
|
|
|
|
std::vector<int> ids;
|
|
|
|
std::vector<std::vector<cv::Point2f> > corners;
|
2019-01-15 16:25:48 +01:00
|
|
|
|
2019-01-09 16:32:16 +01:00
|
|
|
if (arena.IsEmpty())
|
|
|
|
imgTraitment = this->img.clone();
|
|
|
|
else {
|
|
|
|
imgTraitment = CropArena(arena);
|
|
|
|
areneCoor.x = arena.arena.x;
|
|
|
|
areneCoor.y = arena.arena.y;
|
2018-12-14 17:04:42 +01:00
|
|
|
}
|
2019-01-15 16:25:48 +01:00
|
|
|
|
2019-01-09 16:32:16 +01:00
|
|
|
cv::aruco::detectMarkers(imgTraitment, dictionary, corners, ids);
|
|
|
|
if (ids.size() > 0) {
|
|
|
|
for (int i = 0; i < ids.size(); i++) {
|
2018-12-14 17:04:42 +01:00
|
|
|
Position newPos;
|
2019-01-09 16:32:16 +01:00
|
|
|
newPos.center = FindArucoCenter(corners[i]);
|
|
|
|
newPos.direction = FindArucoDirection(corners[i]);
|
2018-12-14 17:04:42 +01:00
|
|
|
newPos.robotId = ids[i];
|
2019-01-09 16:32:16 +01:00
|
|
|
if (!arena.IsEmpty()) {
|
2018-12-14 17:04:42 +01:00
|
|
|
newPos.direction += areneCoor;
|
|
|
|
newPos.center += areneCoor;
|
|
|
|
}
|
2019-01-09 16:32:16 +01:00
|
|
|
newPos.angle = CalculAngle2(newPos.center, newPos.direction);
|
2018-12-14 17:04:42 +01:00
|
|
|
positionList.push_back(newPos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return positionList;
|
2019-01-15 16:25:48 +01:00
|
|
|
#else
|
2018-12-14 17:04:42 +01:00
|
|
|
std::list<Position> robotsFind;
|
2019-01-09 16:32:16 +01:00
|
|
|
std::vector<std::vector<cv::Point> > contours;
|
|
|
|
std::vector<cv::Point> approx;
|
2018-12-14 17:04:42 +01:00
|
|
|
std::vector<cv::Vec4i> hierarchy;
|
|
|
|
|
|
|
|
ImageMat imgTraitment;
|
|
|
|
|
2019-01-09 16:32:16 +01:00
|
|
|
if (arena.IsEmpty()) {
|
|
|
|
imgTraitment = this->img.clone();
|
|
|
|
} else {
|
|
|
|
imgTraitment = this->img(arena.arena);
|
|
|
|
}
|
2018-12-14 17:04:42 +01:00
|
|
|
|
2019-01-09 16:32:16 +01:00
|
|
|
cvtColor(imgTraitment, imgTraitment, CV_RGB2GRAY);
|
|
|
|
threshold(imgTraitment, imgTraitment, 128, 255, CV_THRESH_BINARY);
|
|
|
|
findContours(imgTraitment, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0));
|
2018-12-14 17:04:42 +01:00
|
|
|
|
2019-01-09 16:32:16 +01:00
|
|
|
for (unsigned int i = 0; i < contours.size(); i++) {
|
|
|
|
ImageMat m(contours[i]);
|
|
|
|
cv::approxPolyDP(m, approx, cv::arcLength(ImageMat(contours[i]), true)*0.17, true);
|
2018-12-14 17:04:42 +01:00
|
|
|
|
2019-01-09 16:32:16 +01:00
|
|
|
if (approx.size() == 3 && fabs(cv::contourArea(contours[i])) > 200 && fabs(cv::contourArea(contours[i])) < 700) {
|
|
|
|
cv::Point a, b, c;
|
|
|
|
cv::Point center;
|
2018-12-14 17:04:42 +01:00
|
|
|
|
|
|
|
a = approx[0];
|
|
|
|
b = approx[1];
|
|
|
|
c = approx[2];
|
|
|
|
|
2019-01-09 16:32:16 +01:00
|
|
|
if (!arena.IsEmpty()) { // ajout de l'offset de l'arène
|
|
|
|
a.x += arena.arena.x;
|
|
|
|
a.y += arena.arena.y;
|
|
|
|
b.x += arena.arena.x;
|
|
|
|
b.y += arena.arena.y;
|
|
|
|
c.x += arena.arena.x;
|
|
|
|
c.y += arena.arena.y;
|
2018-12-14 17:04:42 +01:00
|
|
|
}
|
|
|
|
|
2019-01-09 16:32:16 +01:00
|
|
|
center.x = (a.x + b.x + c.x) / 3;
|
|
|
|
center.y = (a.y + b.y + c.y) / 3;
|
2018-12-14 17:04:42 +01:00
|
|
|
Position newPos;
|
2019-01-09 16:32:16 +01:00
|
|
|
newPos.center = center;
|
2018-12-14 17:04:42 +01:00
|
|
|
|
2019-01-09 16:32:16 +01:00
|
|
|
if (EuclideanDistance(center, b) > EuclideanDistance(center, a) && EuclideanDistance(center, b) > EuclideanDistance(center, c)) {
|
|
|
|
newPos.direction = b;
|
2018-12-14 17:04:42 +01:00
|
|
|
//line(img,center,b,Scalar(0,125,0),2,8,0);
|
2019-01-09 16:32:16 +01:00
|
|
|
} else if (EuclideanDistance(center, a) > EuclideanDistance(center, c)) {
|
2018-12-14 17:04:42 +01:00
|
|
|
|
2019-01-09 16:32:16 +01:00
|
|
|
newPos.direction = a;
|
|
|
|
//line(img,center,a,Scalar(0,125,0),2,8,0);
|
|
|
|
} else {
|
|
|
|
newPos.direction = c;
|
2018-12-14 17:04:42 +01:00
|
|
|
//line(img,center,c,Scalar(0,125,0),2,8,0);
|
|
|
|
}
|
2019-01-09 16:32:16 +01:00
|
|
|
|
|
|
|
newPos.angle = CalculAngle(newPos);
|
|
|
|
newPos.robotId = -1; // dumb identifier
|
|
|
|
|
2018-12-14 17:04:42 +01:00
|
|
|
robotsFind.push_back(newPos);
|
|
|
|
}
|
|
|
|
}
|
2019-01-09 16:32:16 +01:00
|
|
|
|
2018-12-14 17:04:42 +01:00
|
|
|
return robotsFind;
|
2019-01-15 16:25:48 +01:00
|
|
|
|
|
|
|
#endif // __WITH_ARUCO__
|
2018-12-14 17:04:42 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 16:25:48 +01:00
|
|
|
/**
|
|
|
|
* Search arena outline in current image
|
|
|
|
* @return Arena object with coordinate of outline, empty if no arena found
|
|
|
|
*/
|
2019-01-04 16:55:48 +01:00
|
|
|
Arena Img::SearchArena() {
|
2018-12-14 17:04:42 +01:00
|
|
|
std::vector<std::vector<cv::Point> > contours;
|
|
|
|
std::vector<cv::Point> approx;
|
|
|
|
std::vector<cv::Vec4i> hierarchy;
|
|
|
|
|
|
|
|
ImageMat imageTrt;
|
2019-01-09 16:32:16 +01:00
|
|
|
cv::cvtColor(this->img, imageTrt, CV_RGB2GRAY); // conversion en niveau de gris
|
|
|
|
cv::threshold(imageTrt, imageTrt, 128, 255, CV_THRESH_BINARY); // Threshold les éléments les plus clair
|
|
|
|
cv::Canny(imageTrt, imageTrt, 100, 200, 3); // detection d'angle
|
2018-12-14 17:04:42 +01:00
|
|
|
|
|
|
|
findContours(imageTrt, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0));
|
2019-01-09 16:32:16 +01:00
|
|
|
for (unsigned int i = 0; i < contours.size(); i++) {
|
2018-12-14 17:04:42 +01:00
|
|
|
approxPolyDP(ImageMat(contours[i]), approx, cv::arcLength(ImageMat(contours[i]), true)*0.1, true);
|
2019-01-09 16:32:16 +01:00
|
|
|
if (approx.size() == 4 && fabs(cv::contourArea(contours[i])) > 100000) {
|
2019-01-04 16:55:48 +01:00
|
|
|
Arena rectangle;
|
|
|
|
rectangle.arena = cv::boundingRect(ImageMat(contours[i]));
|
2018-12-14 17:04:42 +01:00
|
|
|
return rectangle;
|
|
|
|
}
|
|
|
|
}
|
2019-01-15 16:25:48 +01:00
|
|
|
|
2019-01-04 16:55:48 +01:00
|
|
|
return Arena();
|
2018-12-14 17:04:42 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 16:25:48 +01:00
|
|
|
/**
|
|
|
|
* Draw an oriented arrow at robot position
|
|
|
|
* @param robot Position of robot
|
|
|
|
*/
|
|
|
|
void Img::DrawRobot(Position robot) {
|
2019-01-09 16:32:16 +01:00
|
|
|
cv::arrowedLine(this->img, (cv::Point2f)robot.center, (cv::Point2f)robot.direction, cv::Scalar(0, 0, 255), 3, 8, 0);
|
2018-12-14 17:04:42 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 16:25:48 +01:00
|
|
|
/**
|
|
|
|
* Draw an oriented arrow for each position provided
|
|
|
|
* @param robots List of robot positions
|
|
|
|
* @return Number of position drawn
|
|
|
|
*/
|
2019-01-04 16:55:48 +01:00
|
|
|
int Img::DrawAllRobots(std::list<Position> robots) {
|
2019-01-09 16:32:16 +01:00
|
|
|
for (Position robot : robots) {
|
|
|
|
cv::arrowedLine(this->img, (cv::Point2f)robot.center, (cv::Point2f)robot.direction, cv::Scalar(0, 0, 255), 3, 8, 0);
|
2018-12-14 17:04:42 +01:00
|
|
|
}
|
2019-01-15 16:25:48 +01:00
|
|
|
|
2018-12-14 17:04:42 +01:00
|
|
|
return robots.size();
|
|
|
|
}
|
|
|
|
|
2019-01-15 16:25:48 +01:00
|
|
|
/**
|
|
|
|
* Draw arena outline
|
|
|
|
* @param arenaToDraw Arena position
|
|
|
|
*/
|
|
|
|
void Img::DrawArena(Arena arenaToDraw) {
|
2019-01-09 16:32:16 +01:00
|
|
|
cv::rectangle(this->img, arenaToDraw.arena.tl(), arenaToDraw.arena.br(), cv::Scalar(0, 0, 125), 2, 8, 0);
|
2018-12-14 17:04:42 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 16:25:48 +01:00
|
|
|
/**
|
|
|
|
* Crop image around detected arena
|
|
|
|
* @param arena Coordinate of arena
|
|
|
|
* @return Reduced image, focused on arena
|
|
|
|
*/
|
2019-01-09 16:32:16 +01:00
|
|
|
ImageMat Img::CropArena(Arena arena) {
|
|
|
|
return this->img(arena.arena);
|
2018-12-14 17:04:42 +01:00
|
|
|
}
|