Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
600 lines (500 sloc) 15.9 KB
//#define DEBUG_MAIN
//#define DEBUG_SORT
//#define DEBUG_POLY
/*
testhull.cpp
Written by the members of
FIRST Robotics Team #639 - Code Red Robotics
For the 2012 FIRST Robotics Competition.
Based on:
freenectopencv.cpp
Copyright (C) 2010 Arne Bernin <arne@alamut.de>
This code is licensed to you under the terms of the GNU GPL, version 2 or version 3;
see:
http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
http://www.gnu.org/licenses/gpl-3.0.txt
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <cstring>
#include <libfreenect/libfreenect.h>
#include <pthread.h>
#define CV_NO_BACKWARD_COMPATIBILITY
#include <cv.h>
#include <highgui.h>
#include "RobotMath.h"
#include "beagleSender.h"
#define FREENECTOPENCV_WINDOW_N "Normalimage"
#define FREENECTOPENCV_RGB_DEPTH 3 // Currently used for tempImg.
#define FREENECTOPENCV_IR_DEPTH 1 // Current display depth.
#define FREENECTOPENCV_RGB_WIDTH 640
#define FREENECTOPENCV_RGB_HEIGHT 480
class PolyVertices
{
public:
CvPoint* points[4];
CvPoint center;
int dist;
PolyVertices();
void invalidate();
bool isValid();
bool isMalformed();
};
PolyVertices::PolyVertices()
{
points[0] = NULL;
points[1] = NULL;
points[2] = NULL;
points[3] = NULL;
center.x = 0;
center.y = 0;
dist = 0;
}
void PolyVertices::invalidate() { center.x = center.y = 0; }
bool PolyVertices::isValid() { return (center.x != 0 && center.y != 0); }
bool PolyVertices::isMalformed()
{
return false;
}
// Compare centers
bool operator== (const PolyVertices& arg1, const PolyVertices& arg2) { return (arg1.center.x == arg2.center.x && arg1.center.y == arg2.center.y); }
IplImage* rgbimg = 0;
IplImage* tempimg = 0;
pthread_mutex_t mutex_rgb = PTHREAD_MUTEX_INITIALIZER;
pthread_t cv_thread;
// Network socket
int CRRsocket;
// Display images?
bool display;
/*
* Converts n-gons to 4-gons.
* qKey order is, CCW from top right, 3->2->0->1.
*/
void polyToQuad(CvSeq* sequence, PolyVertices *poly, IplImage* img )
{
// Sequence is the hull.
// poly->points is the array that stores the points of the rectangle.
// img is the image we are drawing lines and such on. Not strictly necessary.
#ifdef DEBUG_POLY
printf("Sequence: %d\n", sequence->total);
#endif
/*CALCULATE CENTER*/
int extremes[4]; //minX, maxX, minY, maxY
extremes[0] = ((CvPoint*)cvGetSeqElem(sequence, 0))->x;
extremes[1] = extremes[0];
extremes[2] = ((CvPoint*)cvGetSeqElem(sequence, 0))->y;
extremes[3] = extremes[2];
CvSeqReader seqReader;
cvStartReadSeq(sequence, &seqReader, 0);
for(int i = 0; i < sequence->total; i++)
{
CvPoint* point = (CvPoint*) seqReader.ptr;
if(point->x < extremes[0]) extremes[0] = point->x;
if(point->x > extremes[1]) extremes[1] = point->x;
if(point->y < extremes[2]) extremes[2] = point->y;
if(point->y > extremes[3]) extremes[3] = point->y;
CV_NEXT_SEQ_ELEM(seqReader.seq->elem_size, seqReader);
}
poly->center.x = (extremes[0] + extremes[1])/2;
poly->center.y = (extremes[2] + extremes[3])/2;
if (display) {
cvCircle( img, poly->center, 2, CV_RGB(255,0,255), -1, 8, 0 );
}
#ifdef DEBUG_POLY
printf("Calculated Center (%d): %i, %i\n", sequence->total, poly->center.x, poly->center.y);
#endif
/*CALCULATE DISTANCES FROM CENTER AND CALCULATE MAX DIST IN EACH QUADRANT*/
double distances[4] = {0, 0, 0, 0};
double distance; //current point's distance
int qKey = 0;
CvSeqReader seqReader2;
cvStartReadSeq(sequence, &seqReader2, 0);
for(int i = 0; i < sequence->total; i++)
{
CvPoint* point = (CvPoint*) seqReader2.ptr;
distance = pow((double)point->x - poly->center.x, 2) + pow((double)point->y - poly->center.y, 2);
qKey = ((point->x > poly->center.x ? 1 : 0) + (point->y < poly->center.y ? 2 : 0));
/* FILL POINTS */
if(distance > distances[qKey])
{
poly->points[qKey] = point;
distances[qKey] = distance;
}
CV_NEXT_SEQ_ELEM(seqReader2.seq->elem_size, seqReader2);
}
if(!(poly->points[0] == NULL || poly->points[1] == NULL || poly->points[2] == NULL || poly->points[3] == NULL))
{
/* FILL poly->dist */
poly->dist = pow((double)poly->points[0]->x - poly->center.x, 2) + pow((double)poly->points[0]->y - poly->center.y, 2);
#ifdef DEBUG_POLY
printf("Dist to 0: %d\n", poly->dist);
#endif
// Draw vertices (purple circles)
if (display) {
cvCircle( img, *poly->points[0], 3, CV_RGB(255,0,255), -1, 8, 0 );
cvCircle( img, *poly->points[1], 3, CV_RGB(255,0,255), -1, 8, 0 );
cvCircle( img, *poly->points[2], 3, CV_RGB(255,0,255), -1, 8, 0 );
cvCircle( img, *poly->points[3], 3, CV_RGB(255,0,255), -1, 8, 0 );
}
}
}
// Return whether we want to delete poly. Subroutine of FilterInnerRects.
bool FilterSub(vector<PolyVertices> &list, PolyVertices poly)
{
for (int i = 0; i < list.size(); i++)
{
double result = pow((double)list[i].center.x - (double)poly.center.x, 2) + pow((double)list[i].center.y - (double)poly.center.y, 2);
if ((result < 50 * 50) && (list[i].dist != poly.dist))
if (list[i].dist > poly.dist)
return true;
}
return false;
}
// Remove rectangles enclosed by others.
void FilterInnerRects(vector<PolyVertices> &list)
{
vector<PolyVertices>::iterator p = list.begin();
while (p < list.end())
{
if (FilterSub(list, *p))
p = list.erase(p);
else
++p;
}
/* Version without goto:
vector<PolyVertices>::iterator p = list.begin();
while (p < list.end()) {
if (FilterSub(list, *p))
{
fflush(stdout);
p = list.erase(p);
} else {
++p;
}
}
*/
}
// The word "must" in the following function should be interpreted to mean "if we are not missing more than
// however many rectangles we think we are missing." There is really no provision yet for missing >1, but it
// shouldn't be too much of an issue.
void SortRects(vector<PolyVertices> &list)
{
// Fill with invalid structs to ensure no segfaults.
// TODO: if we decide that missing >1 is not worth coding, we could return here if 4 - list.size() is >1.
#ifdef DEBUG_SORT
printf("Empty structs pushed: %d\n", 4 - list.size());
#endif
for (int i = list.size(); i < 4; i++)
{
PolyVertices temp;
list.push_back(temp);
}
PolyVertices top, left, right, bottom;
top = left = right = bottom = list[0];
/* FILL WITH PRELIM. DATA */
for (vector<PolyVertices>::iterator p = list.begin(); p < list.end(); p++)
{
if ((*p).isValid())
{
if ((*p).center.y < top.center.y)
top = *p;
if ((*p).center.y > bottom.center.y)
bottom = *p;
if ((*p).center.x < left.center.x)
left = *p;
if ((*p).center.x > right.center.x)
right = *p;
}
}
/* FIND WHICH RECTANGLE IS MISSING */
if (top == left || top == right)
{
// CASE: top and left are ambiguous
if (abs(top.center.x - left.center.x) < 50)
{
// right and bottom must be correct
list[2] = right;
list[3] = bottom;
if (abs(top.center.x - bottom.center.x) < 50) // both are top
{
list[0] = top;
list[1].invalidate();
}
else // both are left
{
list[0].invalidate();
list[1] = left;
}
}
// CASE: top and right are ambiguous
else if (abs(top.center.x - right.center.x) < 50)
{
// left and bottom must be correct
list[1] = left;
list[3] = bottom;
if (abs(top.center.x - bottom.center.x) < 50) // both are top
{
list[0] = top;
list[2].invalidate();
}
else // both are right
{
list[0].invalidate();
list[2] = right;
}
}
}
else if (bottom == left || bottom == right)
{
// CASE: bottom and left are ambiguous
if (abs(bottom.center.x - left.center.x) < 50)
{
// top and right must be correct
list[0] = top;
list[2] = right;
if (abs(top.center.x - bottom.center.x) < 50) // both are bottom
{
list[1].invalidate();
list[3] = bottom;
}
else // both are left
{
list[1] = left;
list[3].invalidate();
}
}
// CASE: bottom and right are ambiguous
else if (abs(bottom.center.x - right.center.x) < 50)
{
// top and left must be correct
list[0] = top;
list[1] = left;
if (abs(top.center.x - bottom.center.x) < 50) // both are bottom
{
list[2].invalidate();
list[3] = bottom;
}
else // both are right
{
list[2] = right;
list[3].invalidate();
}
}
}
else // none missing.
{
list[0] = top;
list[1] = left;
list[2] = right;
list[3] = bottom;
}
#ifdef DEBUG_SORT
for (int i = 0; i < 4; i++)
{
printf("[%d]: (%d, %d)\n", i, list[i].center.x, list[i].center.y);
}
#endif
}
// callback for rgbimage, called by libfreenect
void rgb_cb(freenect_device *dev, void *rgb, uint32_t timestamp)
{
pthread_mutex_lock( &mutex_rgb );
memcpy(rgbimg->imageData, rgb, FREENECT_VIDEO_RGB_SIZE);
pthread_mutex_unlock( &mutex_rgb );
}
/*
* Main thread for Kinect input, vision processing, and network send - everything, really.
*/
void *cv_threadfunc (void *ptr) {
// Images for openCV
IplImage* timg = cvCloneImage(rgbimg); // Image we do our processing on
IplImage* dimg = cvCloneImage(timg); // Image we draw on
CvSize sz = cvSize( timg->width & -2, timg->height & -2);
IplImage* outimg = cvCreateImage(sz, 8, 3);
// Mem. mgmt. Remember to clear each time we run loop.
CvMemStorage* storage = cvCreateMemStorage(0);
// Set region of interest
cvSetImageROI(timg, cvRect(0, 0, sz.width, sz.height));
if (display) { cvSetImageROI(dimg, cvRect(0, 0, sz.width, sz.height)); }
// Open network socket.
CRRsocket = openSocket();
if (CRRsocket < 0) pthread_exit(NULL);
/*
* MAIN LOOP
*/
while (1)
{
// Sequence to run ApproxPoly on
CvSeq* polyseq = cvCreateSeq( CV_SEQ_KIND_CURVE | CV_32SC2, sizeof(CvSeq), sizeof(CvPoint), storage );
CvSeq* contours; // Raw contours list
CvSeq* hull; // Current convex hull
int hullcount; // # of points in hull
/* PULL RAW IMAGE FROM KINECT */
pthread_mutex_lock( &mutex_rgb );
if (display) { cvCopy(rgbimg, dimg, 0); }
cvCopy(rgbimg, timg, 0);
pthread_mutex_unlock( &mutex_rgb );
/* DILATE */
IplConvKernel* element = cvCreateStructuringElementEx(3, 3, 1, 1, 0);
IplConvKernel* element2 = cvCreateStructuringElementEx(5, 5, 2, 2, 0);
cvDilate(timg, timg, element2, 1);
cvErode(timg, timg, element, 1);
/* THRESHOLD*/
cvThreshold(timg, timg, 100, 255, CV_THRESH_BINARY);
/* OUTPUT PROCESSED OR RAW IMAGE (FindContours destroys image) */
if (display) { cvCvtColor(dimg, outimg, CV_GRAY2BGR); }
/* CONTOUR FINDING */
cvFindContours(timg, storage, &contours, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));
/* CONVEX HULL + POLYGON APPROXIMATION + CONVERT TO RECTANGLE + FILTER FOR INVALID RECTANGLES */
// Store points to draw line between
CvPoint* draw1;
CvPoint* draw2;
vector<PolyVertices> rectangleList;
while (contours) // Run for all polygons
{
// List of raw rectangles
PolyVertices fullrect;
// Filter noise
if (fabs(cvContourArea(contours, CV_WHOLE_SEQ)) > 600)
{
// Get convex hull
hull = cvConvexHull2( contours, storage, CV_CLOCKWISE, 1 );
hullcount = hull->total;
// Draw hull (red line)
if (display) {
draw1 = (CvPoint*)cvGetSeqElem(hull, hullcount - 1);
for (int i = 0; i < hullcount; i++)
{
draw2 = (CvPoint*)cvGetSeqElem( hull, i );
cvLine( outimg, *draw1, *draw2, CV_RGB(255,0,0), 1, 8, 0 );
draw1 = draw2;
}
}
// Convert polys from convex hull to rectangles, fill list
polyToQuad(hull, &fullrect, outimg);
// Filter for bad rectangles
if(!(fullrect.points[0] == NULL || fullrect.points[1] == NULL ||
fullrect.points[2] == NULL || fullrect.points[3] == NULL)
&& !fullrect.isMalformed())
{
/* FILL rectangleList */
rectangleList.push_back(fullrect);
#ifdef DEBUG_MAIN
printf("RESULT: (%d,%d), (%d,%d), (%d,%d), (%d,%d)\n",
fullrect.points[0]->x, fullrect.points[0]->y,
fullrect.points[1]->x, fullrect.points[1]->y,
fullrect.points[2]->x, fullrect.points[2]->y,
fullrect.points[3]->x, fullrect.points[3]->y);
fflush(stdout);
#endif
}
}
cvClearSeq(polyseq);
contours = contours->h_next;
}
/* FILTER OVERLAPPING RECTANGLES */
FilterInnerRects(rectangleList);
/* SORT INTO CORRECT BUCKET */
SortRects(rectangleList);
/* DRAW & PROCESS MATH; FILL SEND STRUCT */
// TODO: Might want to make the math stuff static for efficiency.
RobotMath robot;
TrackingData outgoing;
memset(&outgoing, 0, sizeof(TrackingData));
// Fill packets
// Packet fields are unsigned 16bit integers, so we need to scale them up
// Currently both dist and angle scaled 100x (hundredths precision)
// NOTE:
// Currently correct results are only calculated by using bottom basket and constant for top.
if (rectangleList[0].isValid())
{
outgoing.distHigh = 100 * robot.GetDistance(*(rectangleList[0].points[2]), *(rectangleList[0].points[3]), 0);
outgoing.angleHigh = 100 * robot.GetAngle(*(rectangleList[0].points[2]), *(rectangleList[0].points[3]));
}
// if (rectangleList[1].isValid())
// {
// outgoing.distLeft = 100 * robot.GetDistance(*(rectangleList[1].points[2]), *(rectangleList[1].points[3]), 1);
// outgoing.angleLeft = 100 * robot.GetAngle(*(rectangleList[1].points[2]), *(rectangleList[1].points[3]));
// }
// if (rectangleList[2].isValid())
// {
// outgoing.distRight = 100 * robot.GetDistance(*(rectangleList[2].points[2]), *(rectangleList[2].points[3]), 2);
// outgoing.angleRight = 100 * robot.GetAngle(*(rectangleList[2].points[2]), *(rectangleList[2].points[3]));
// }
if (rectangleList[3].isValid())
{
outgoing.distLow = 100 * robot.GetDistance(*(rectangleList[3].points[2]), *(rectangleList[3].points[3]), 3);
outgoing.angleLow = 100 * robot.GetAngle(*(rectangleList[3].points[2]), *(rectangleList[3].points[3]));
}
// Draw filtered rects (thick blue line)
if (display) {
for (int i = 0; i < 4; i++)
{
if (outimg && rectangleList[i].isValid())
{
cvLine( outimg, *(rectangleList[i].points[3]), *(rectangleList[i].points[2]), CV_RGB(0,0,255), 2, 8, 0 );
cvLine( outimg, *(rectangleList[i].points[2]), *(rectangleList[i].points[0]), CV_RGB(0,0,255), 2, 8, 0 );
cvLine( outimg, *(rectangleList[i].points[0]), *(rectangleList[i].points[1]), CV_RGB(0,0,255), 2, 8, 0 );
cvLine( outimg, *(rectangleList[i].points[1]), *(rectangleList[i].points[3]), CV_RGB(0,0,255), 2, 8, 0 );
}
}
}
#ifdef DEBUG_MAIN
printf("Top distance: %d\n", outgoing.distHigh);
printf("Top angle: %d\n\n", outgoing.angleHigh);
#endif
CvPoint cent1 = cvPoint(320, 0);
CvPoint cent2 = cvPoint(320, 480);
if (display) { cvLine( outimg, cent1, cent2, CV_RGB(0,255,0), 1, 8, 0 ); }
/* SEND TO CRIO */
sendData(&outgoing, CRRsocket);
if( cvWaitKey( 15 )==27 )
{
// Empty for now.
}
/* DISPLAY */
if (display) { cvShowImage (FREENECTOPENCV_WINDOW_N,outimg); }
/* CLEANUP */
cvClearMemStorage(storage);
}
pthread_exit(NULL);
}
/*
* Main method; primarily just initializes our thread and handles Kinect details.
*/
int main(int argc, char **argv)
{
freenect_context *f_ctx;
freenect_device *f_dev;
display = 0;
int res = 0;
int die = 0;
printf("Code Red Kinect Vision init\n");
if (argc > 1 && strcmp(argv[1],"--display") == 0) {
display = 1;
}
if (freenect_init(&f_ctx, NULL) < 0) {
printf("freenect_init() failed\n");
return 1;
}
if (freenect_open_device(f_ctx, &f_dev, 0) < 0) {
printf("Could not open device\n");
return 1;
}
if (display) { cvNamedWindow( FREENECTOPENCV_WINDOW_N, CV_WINDOW_AUTOSIZE ); }
rgbimg = cvCreateImage(cvSize(FREENECTOPENCV_RGB_WIDTH, FREENECTOPENCV_RGB_HEIGHT), IPL_DEPTH_8U, FREENECTOPENCV_IR_DEPTH);
tempimg = cvCreateImage(cvSize(FREENECTOPENCV_RGB_WIDTH, FREENECTOPENCV_RGB_HEIGHT), IPL_DEPTH_8U, FREENECTOPENCV_RGB_DEPTH);
// create opencv display thread
res = pthread_create(&cv_thread, NULL, cv_threadfunc, (void*) tempimg);
if (res) {
printf("pthread_create failed\n");
return 1;
}
freenect_set_video_callback(f_dev, rgb_cb);
freenect_set_video_format(f_dev, FREENECT_VIDEO_IR_8BIT);
printf("init done\n");
freenect_start_video(f_dev);
while(!die && freenect_process_events(f_ctx) >= 0 );
}