blob: 856e12bd4ae37514ac4d005a49dfc964ef8ec28f [file] [log] [blame]
#include "vision/CameraProcessor.h"
#include "aos/common/logging/logging.h"
//#define LOCAL_DEBUG 1
// create a new target
Target::Target(std::vector<cv::Point> new_contour,
std::vector<cv::Point> new_raw_contour,
FullRect new_rect, int new_weight, bool _is_90) {
this_contour = new_contour;
raw_contour = new_raw_contour;
rect = new_rect;
weight = new_weight;
height = getHeight(_is_90);
}
// calculate distance to target
double Target::getHeight(bool is_90) {
// The 780.3296 is at full resolution 640x480, and we need
// to scale back to 320x240
//static const double cam_l = 780.3296 / 2.0;
////static const double cam_d = 20.78096;
double height;
if (is_90) {
height = ((rect.ul.x - rect.ur.x) +
(rect.bl.x - rect.br.x)) / 2.0;
} else {
height = ((rect.ur.y + rect.ul.y) -
(rect.br.y + rect.bl.y)) / 2.0;
}
//return cam_l * 12.0 / height;
return height;
}
void Target::refineTarget() {
printf("okay refined\n");
}
FullRect::FullRect() {
ur.x = -1;
ur.y = -1;
ul.x = -1;
ul.y = -1;
br.x = -1;
br.y = -1;
bl.x = -1;
bl.y = -1;
}
// turns a contour into easier to access structure
FullRect calcFullRect(std::vector<cv::Point> *contour){
FullRect rect;
for(int i=0; i<4; i++){
cv::Point2f pt = (*contour)[i];
rect.centroid.x += pt.x;
rect.centroid.y += pt.y;
}
rect.centroid.x /= 4;
rect.centroid.y /= 4;
for(int i=0; i<4; i++){
cv::Point2f pt = (*contour)[i];
if(pt.y > rect.centroid.y ){
if(pt.x > rect.centroid.x){
if (rect.ul.x < 0) {
rect.ul = pt;
} else {
rect.ur = pt;
}
}else{
if (rect.ur.x < 0) {
rect.ur = pt;
} else {
rect.ul = pt;
}
}
if (rect.ul.x > 0 && rect.ur.x > 0) {
// both are set, so if we got it wrong correct it here
if (rect.ul.x > rect.ur.x) {
pt = rect.ur;
rect.ur = rect.ul;
rect.ul = pt;
}
}
}else{
if(pt.x > rect.centroid.x){
if (rect.bl.x < 0) {
rect.bl = pt;
} else {
rect.br = pt;
}
}else{
if (rect.br.x < 0) {
rect.br = pt;
} else {
rect.bl = pt;
}
}
if (rect.bl.x > 0 && rect.br.x > 0) {
// both are set, so if we got it wrong correct it here
if (rect.bl.x > rect.br.x) {
pt = rect.br;
rect.br = rect.bl;
rect.bl = pt;
}
}
}
}
return rect;
}
// quickly remove targets that do not fit a very broad set of constraints
bool cullObvious(FullRect rect, double outside_size){
// first check that could see a target this size
// Calculated from dave's simulation, shloud be 850 and 72000 if filled
if((outside_size < 500) || (outside_size > 90000)){
return false;
}
// Targets on the edge are at best inaccurate.
// In this case, we just want to point the right way,
// so this is no longer a valid assumption.
/*if( rect.ur.x < 2 || rect.ur.y < 2 || rect.ur.x > 637 || rect.ur.y > 477 ||
rect.ul.x < 2 || rect.ul.y < 2 || rect.ul.x > 637 || rect.ul.y > 477 ||
rect.br.x < 2 || rect.br.y < 2 || rect.br.x > 637 || rect.br.y > 477 ||
rect.bl.x < 2 || rect.bl.y < 2 || rect.bl.x > 637 || rect.bl.y > 477){
return false;
}*/
// make sure the sides are close to the right ratio of a rect
// some really odd shapes get confusing
double ratio = norm(rect.ur-rect.ul)/norm(rect.br-rect.bl);
if( ratio < .7 || ratio > 1.4 ) {
return false;
}
ratio = norm(rect.ur-rect.br)/norm(rect.ul-rect.bl);
if( ratio < .7 || ratio > 1.4 ) {
return false;
}
return true;
}
// sum over values between these two points and normalize
// see Bresenham's Line Algorithm for the logic of moving
// over all the pixels between these two points.
double ProcessorData::calcHistComponent(
cv::Point2i start,
cv::Point2i end,
cv::Mat thresh_img){
int dx = abs(end.x - start.x);
int dy = abs(end.y - start.y);
int sx = (start.x < end.x) ? 1 : -1;
int sy = (start.y < end.y) ? 1 : -1;
int error = dx-dy;
int total = 0;
int value = 0;
int total_error;
#if LOCAL_DEBUG
IplImage gd = *global_display;
#endif
IplImage ti = thresh_img;
while(1){
total++;
uchar* ptr = (uchar*) (ti.imageData + start.y * ti.widthStep + start.x);
if((int) *ptr) value++;
// draw line checked
#if LOCAL_DEBUG
uchar* ptr2 = (uchar*) (gd.imageData + start.y * gd.widthStep + start.x*3);
*ptr2++ = 0;
*ptr2++ = 255;
*ptr2 = 0;
#endif
if(start.x == end.x && start.y == end.y) break;
total_error = 2 * error;
if(total_error > -dy){
error -= dy;
start.x += sx;
}
if(total_error < dx){
error += dx;
start.y += sy;
}
}
return (double)value/(double)total;
}
// just a distance function
double chiSquared(int length, double* histA, double* histB){
double sum = 0;
for(int i=0; i<length;i++){
double diff = *(histB+i) - *(histA+i);
sum += (diff * diff) / *(histA+i);
}
return sum;
}
// euclidiean dist function
double L2_dist(int length, double* histA, double* histB){
double sum = 0;
for(int i=0; i<length;i++){
double diff = *(histB+i) - *(histA+i);
sum += (diff * diff);
}
return sqrt(sum);
}
// calc and compare the histograms to the desired
double ProcessorData::checkHistogram(FullRect rect, cv::Mat thresh_img){
// found horiz histogram
double hist_lr[HIST_SIZE];
// step size along left edge
cv::Point2f delta_left = (rect.ul - rect.bl)*HIST_SIZE_F;
// step size along right edge
cv::Point2f delta_right = (rect.ur - rect.br)*HIST_SIZE_F;
// sum each left to right line for the histogram
for(int i=0; i<HIST_SIZE; i++){
hist_lr[i] = calcHistComponent(rect.bl+i*delta_left,
rect.br+i*delta_right,thresh_img);
}
double check_vert = L2_dist(HIST_SIZE, vert_hist, hist_lr);
// found vert histogram
double hist_ub[HIST_SIZE];
// step size along bottom edge
cv::Point2f delta_bottom = (rect.bl - rect.br)*HIST_SIZE_F;
// step size along top edge
cv::Point2f delta_top = (rect.ul - rect.ur)*HIST_SIZE_F;
// sum each top to bottom line for the histogram
for(int i=0; i<HIST_SIZE; i++){
hist_ub[i] = calcHistComponent(rect.ur+i*delta_top,
rect.br+i*delta_bottom,thresh_img);
}
double check_horz = L2_dist(HIST_SIZE, horz_hist, hist_ub);
// average the two distances
double check = (check_vert + check_horz)/2.0;
return check;
}
// return smallest
template<class T> inline T Min3(T x, T y, T z) {
return y <= z ? (x <= y ? x : y)
: (x <= z ? x : z);
}
// return largest
template<class T> inline T Max3(T x, T y, T z) {
return y >= z ? (x >= y ? x : y)
: (x >= z ? x : z);
}
// transforms the contour
void makeConvex(std::vector<cv::Point> *contour){
std::vector<cv::Point2i> hull;
convexHull(*contour, hull, false);
*contour = hull;
}
// basic init
ProcessorData::ProcessorData(int width, int height, bool is_90_) {
is_90 = is_90_;
// series b images from nasa
h1=79; s1=53; v1=82;
h2=200; s2=255; v2=255;
// For images from Jerry
//h1=79; s1=0; v1=11;
//h2=160; s2=255; v2=255;
img_width = width;
img_height = height;
buffer_size = img_height * img_width * 3;
#if LOCAL_DEBUG
global_display = cvCreateImage(cvSize(width, height),
IPL_DEPTH_8U, 3);
#endif
grey_image = cvCreateImage(cvSize(width, height),
IPL_DEPTH_8U, 1);
grey_mat = new cv::Mat(grey_image);
// calculate a desired histogram before we start
int j = 0;
for(double i=0; j<HIST_SIZE; i+=HIST_SIZE_F){
if (is_90) {
if(i < 2.0/12.0 || i > (1.0-2.0/12.0) ) horz_hist[j] = 1;
else horz_hist[j] = 0.10;
if(i < 2.0/24.0 || i > (1.0-2.0/24.0) ) vert_hist[j] = 1;
else vert_hist[j] = 4.0/24.0;
} else {
if(i < 2.0/12.0 || i > (1.0-2.0/12.0) ) vert_hist[j] = 1;
else vert_hist[j] = 0.10;
if(i < 2.0/24.0 || i > (1.0-2.0/24.0) ) horz_hist[j] = 1;
else horz_hist[j] = 4.0/24.0;
}
j++;
}
src_header_image = cvCreateImage(cvSize(width, height),
IPL_DEPTH_8U, 3);
}
// throw stuff away
ProcessorData::~ProcessorData() {
cvReleaseImage(&grey_image);
cvReleaseImage(&src_header_image);
delete(grey_mat);
}
// reset processor data freeing as little as possible.
void ProcessorData::clear() {
target_list.clear();
contour_pairs.clear();
hierarchy.clear();
}
// r,g,b values are from 0 to 255
// h = [0,255], s = [0,255], v = [0,255]
// if s == 0, then h = 0 (undefined)
void ProcessorData::RGBtoHSV(uchar r, uchar g, uchar b,
uchar *h, uchar *s, uchar *v ) {
uchar min, max, delta;
min = Min3( r, g, b );
max = Max3( r, g, b );
*v = max;
delta = max - min;
if (max == 0 || delta == 0) {
*s = 0;
*h = 0;
return;
}
*s = (255 * long(delta))/max;
if (max == r) {
*h = 0 + 43*(g - b)/(delta);
} else if (max == g) {
*h = 85 + 43*(b - r)/(delta);
} else {
*h = 171 + 43*(r - g)/(delta);
}
}
// keep anything that is in our HVS wedge
// by far the longest running function in this code
void ProcessorData::threshold(uchar* buffer) {
#if LOCAL_DEBUG
uchar * draw_buffer = (uchar *) global_display->imageData;
#endif
for (int i = 0, j = 0; i < (buffer_size); i+= 3, j++) {
uchar r = buffer[i + 2];
uchar g = buffer[i + 1];
uchar b = buffer[i + 0];
uchar h, s, v;
RGBtoHSV(r, g, b, &h, &s, &v);
if (g > 128) {
#if LOCAL_DEBUG
draw_buffer[i + 0] = 120;
draw_buffer[i + 1] = 80;
draw_buffer[i + 2] = 70;
#endif
grey_image->imageData[j] = 255;
} else if (h == 0 && s == 0 && v >= v1 && v <= v2) {
// Value thresholds invalid pixels.
#if LOCAL_DEBUG
draw_buffer[i + 0] = 200;
draw_buffer[i + 1] = 50;
draw_buffer[i + 2] = 100;
#endif
grey_image->imageData[j] = 255;
} else if (h >= h1 && h <= h2 && v >= v1 &&
v <= v2 && s >= s1 && s <= s2){
// HSV Thresholded image.
#if LOCAL_DEBUG
draw_buffer[i + 0] = 255;
draw_buffer[i + 1] = 0;
draw_buffer[i + 2] = 0;
#endif
grey_image->imageData[j] = 255;
} else {
// Display the unmodified image.
#if LOCAL_DEBUG
draw_buffer[i + 0] = buffer[i + 0];
draw_buffer[i + 1] = buffer[i + 1];
draw_buffer[i + 2] = buffer[i + 2];
#endif
grey_image->imageData[j] = 0;
}
}
}
// run find contours and try to make them squares
void ProcessorData::getContours() {
std::vector<std::vector<cv::Point> > raw_contours;
//cv::findContours(*grey_mat, raw_contours, hierarchy, CV_RETR_LIST,
// CV_CHAIN_APPROX_SIMPLE);
cv::findContours(*grey_mat, raw_contours, hierarchy, CV_RETR_EXTERNAL,
CV_CHAIN_APPROX_SIMPLE);
#if LOCAL_DEBUG
cv::Mat global_mat(global_display);
cv::Scalar color(255,0,0);
drawContours(global_mat,raw_contours,-1,color,1);
std::vector<std::vector<cv::Point> > p_contours;
#endif
if (!raw_contours.empty()) {
std::vector<std::vector<cv::Point2i> >::iterator contour_it;
for (contour_it=raw_contours.begin();
contour_it != raw_contours.end();
contour_it++) {
// make the contours convex
makeConvex(&(*contour_it));
std::vector<cv::Point> contour;
//then make them rectangle
approxPolyDP(*contour_it, contour, 9, true);
// stick the raw and processed contours together
std::pair<std::vector<cv::Point>,
std::vector<cv::Point> > a_pair(
*contour_it, contour);
#if LOCAL_DEBUG
p_contours.push_back(contour);
#endif
// and put them in a list
contour_pairs.push_back(a_pair);
}
}
#if LOCAL_DEBUG
cv::Scalar color2(0,0,255);
drawContours(global_mat,p_contours,-1,color2,CV_FILLED);
#endif
}
// filter the contours down to a list of targets
void ProcessorData::filterToTargets() {
std::vector<std::pair<
std::vector<cv::Point>,
std::vector<cv::Point> > >::iterator contour_it;
for(contour_it=contour_pairs.begin();
contour_it != contour_pairs.end();
contour_it++){
double check = 0;
std::vector<cv::Point> raw_contour = std::get<0>(*contour_it);
std::vector<cv::Point> contour = std::get<1>(*contour_it);
FullRect rect = calcFullRect(&contour);
if(contour.size() == 4 &&
cullObvious(rect, contourArea(contour)) &&
(check = checkHistogram(rect, *grey_mat)) <= HIST_MATCH){
// now we have a target, try to improve the square
#if LOCAL_DEBUG
/* printf("________\n");
printf("\tcont= %d raw= %d\n",
(int)contour.size(), (int)raw_contour.size());
std::vector<cv::Point2i>::iterator point_it;
for(point_it=raw_contour.begin();
point_it != raw_contour.end(); point_it++){
printf("(%d,%d)", point_it->x, point_it->y);
}
printf("\n");*/
#endif
target_list.push_back(Target(contour,
raw_contour, rect, check, is_90));
}
if (contour.size() == 4 && cullObvious(rect, contourArea(contour))) {
LOG(DEBUG, "check= %.2f\n", check);
}
}
}