/* * horizon_steerer.cpp * * Copyright 2012 Florian Jung * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License Version 3 * as published by the Free Software Foundation. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include #include #include #include #include #include "horizon_steerer.h" #include "util.h" using namespace std; using namespace cv; HorizonSteerer::HorizonSteerer(int xlen_, int ylen_) { xlen=xlen_; ylen=ylen_; my_contour_map=new int*[xlen]; for (int i=0;i 0.0 && steering_point != origin_point) { // negative values mean "steer left", positive mean "steer right" double angle = -atan2(origin_point.x - steering_point.x, origin_point.y- steering_point.y) *180/3.1415192654; if (angle > 90 || angle < -90) { confidence=0.0; steer_value=0.0; } else { steer_value=flopow(angle/90.0,1)/2 ; } } else { steer_value=0.0; } imshow("drawing",drawing); } double HorizonSteerer::get_steer_data() { return steer_value; } double HorizonSteerer::get_confidence() { return confidence; } static void set_pixel(Mat m, Point p, Scalar color) { line(m,p,p,color); } int HorizonSteerer::find_intersection_index(int x0, int y0, int x1, int y1, int** contour_map, bool stop_at_endpoint) // bresenham aus der dt. wikipedia // returns: the point's index where the intersection happened, or a negative number if no intersection. { int dx = abs(x1-x0), sx = x0=xlen || y0<0 || y0>=ylen) return -1; if (contour_map[x0][y0]>0) return contour_map[x0][y0]; // found intersection? if (y0+10) return contour_map[x0][y0+1]; if (x0+10) return contour_map[x0+1][y0]; if (stop_at_endpoint && x0==x1 && y0==y1) return -1; e2 = 2*err; if (e2 > dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */ if (e2 < dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */ } return -1; } static void hue2rgb(float hue, int* r, int* g, int* b) { double ff; int i; if (hue >= 360.0) hue = 0.0; hue /= 60.0; i = (int)hue; ff = hue - i; int x=ff*255; switch(i) { case 0: *r = 255; *g = x; *b = 0; break; case 1: *r = 255-x; *g = 255; *b = 0; break; case 2: *r = 0; *g = 255; *b = x; break; case 3: *r = 0; *g = 255-x; *b = 255; break; case 4: *r = x; *g = 0; *b = 255; break; case 5: default: *r = 255; *g = 0; *b = 255-x; break; } } static double linear(double x, double x1, double y1, double x2, double y2, bool clip=false, double clipmin=INFINITY, double clipmax=INFINITY) { if (clipmin==INFINITY) clipmin=y1; if (clipmax==INFINITY) clipmax=y2; if (clipmin>clipmax) { int tmp=clipmin; clipmin=clipmax; clipmax=tmp; } double result = (y2-y1)*(x-x1)/(x2-x1)+y1; if (clip) { if (result>clipmax) return clipmax; else if (result(row); for (int col=0; col(row); for (int col=0; col200) tmp=200; if (*data==largest_region) tmp=255; *data=tmp; } data++; } } } double HorizonSteerer::only_retain_largest_region(Mat img, int* size) // img is a binary image // in *size, if non-NULL, the size of the largest area is stored. // returns: ratio between the second-largest and largest region // 0.0 means "that's the only region", 1.0 means "both had the same size!" // negative values mean "no area at all!" // can be interpreted as 1.0 - "confidence". { int n_regions = annotate_regions(img); if (n_regions == 0) return -1; // calculate the area of each region int* area_cnt = new int[n_regions]; // TODO: hier könnt man optimieren, wenn area_cnt==1 for (int i=0;i(row); for (int col=0; col=maxa) { maxa=area_cnt[i]; maxi2=maxi; maxi=i; } } // lösche alle bis auf die größte fläche for (int row = 0; row(row); for (int col=0; col& HorizonSteerer::prepare_and_get_contour(vector< vector >& contours, const vector& hierarchy, int* low_y, int* low_idx, int* high_y, int* first_nonbottom_idx) { assert(low_y!=NULL); assert(low_idx!=NULL); assert(high_y!=NULL); assert(first_nonbottom_idx!=NULL); assert(contours.size()>=1); // find index of our road contour int road_contour_idx=-1; for (road_contour_idx=0; road_contour_idx=0 && road_contour_idx0); vector& contour = contours[road_contour_idx]; // just a shorthand // our road is now in contour. // find highest and lowest contour point. (where "low" means high y-coordinate) *low_y=0; *low_idx=0; *high_y=ylen; for (int j=0;j *low_y) { *low_y=contour[j].y; *low_idx=j; } if (contour[j].y < *high_y) { *high_y=contour[j].y; } } // make the contour go "from bottom upwards and then downwards back to bottom". std::rotate(contour.begin(),contour.begin()+*low_idx,contour.end()); *first_nonbottom_idx = 0; for (;*first_nonbottom_idx=0); if (!(*first_nonbottom_idx& contour, int** contour_map) { assert(contour_map!=NULL); for (int j=0;j& contour, int first_nonbottom_idx, int smoothen_middle, int smoothen_bottom) { // calculate directional angle for each nonbottom contour point double* angles = new double[contour.size()]; for (int j=first_nonbottom_idx; j= contour.size()) j1-=contour.size(); int j2=(j-smoothen); while (j2 < 0) j2+=contour.size(); // calculate angle, adjust it to be within [0, 360) angles[j] = atan2(contour[j1].y - contour[j2].y, contour[j1].x - contour[j2].x) * 180/3.141592654; if (angles[j]<0) angles[j]+=360; } return angles; } // returns a new double[] or NULL, if contour is too small double* HorizonSteerer::calc_angle_deriv(double* angles, int first_nonbottom_idx, int size, int ang_smooth) { if (first_nonbottom_idx+ang_smooth >= size-ang_smooth) return NULL; // not enough data for deriving! // calculate derivative of angle for each nonbottom contour point double* angle_derivative = new double[size]; for (int j=first_nonbottom_idx+ang_smooth; j=360) ang_diff-=360; if (ang_diff>=180) ang_diff=360-ang_diff; angle_derivative[j] = (double)ang_diff / ang_smooth; } // poorly extrapolate the ang_smooth margins for (int j=first_nonbottom_idx; j& contour, double* angle_derivative, int high_y, int first_nonbottom_idx, Mat& drawing, int* bestquality_j_out, int* bestquality_width_out, int* bestquality_out, int* bestquality_max_out) { assert(bestquality_out!=NULL); assert(bestquality_j_out!=NULL); assert(bestquality_width_out!=NULL); double lastmax=-999999; // TODO that sucks :/ double bestquality=0.0; double bestquality_max=0.0; int bestquality_j=-1; int bestquality_width=0; #define MAX_HYST 0.8 // search for "maximum regions"; i.e. intervals [a,b] with // ang_deriv[i] >= MAX_HYST * max_deriv \forall i \in [a,b] and // ang_deriv[a-1,2,3], ang_deriv[b+1,2,3] < MAX_HYST * max_deriv // where max_deriv = max_{i \in [a,b]} ang_deriv[i]; // if contour.size() is too small, the for loop is never executed, and bestquality_j stays -1. for (int j=3; j<(int)contour.size()-3; j++) { // search forward for a maximum, and the end of a maximum region. if (angle_derivative[j] > lastmax) lastmax=angle_derivative[j]; if (angle_derivative[j] < MAX_HYST*lastmax && // found the end of the max. region angle_derivative[j+1] < MAX_HYST*lastmax && angle_derivative[j+2] < MAX_HYST*lastmax) { if (lastmax > 7) // threshold the maximum. { // search backward for the begin of that maximum region int j0; for (j0=j-1; j0>=0; j0--) if (angle_derivative[j0] < MAX_HYST*lastmax && angle_derivative[j0-1] < MAX_HYST*lastmax && angle_derivative[j0-2] < MAX_HYST*lastmax) break; // maximum region is [j0; j] double median_of_max_region = (double)angle_derivative[(j+j0)/2]; // calculate quality of that maximum. quality is high, if // 1) the maximum has a high value AND // 2) the corresponding point's y-coordinates are near the top image border AND // 3) the corresponding point's x-coordinates are near the middle of the image, if in doubt int middle_x = xlen/2; int distance_from_middle_x = abs(xlen/2 - contour[j].x); double quality = lastmax * linear( contour[j].y, high_y, 1.0, high_y+ (ylen-high_y)/10, 0.0, true) // excessively punish points far away from the top border * linear( distance_from_middle_x, 0.8*middle_x, 1.0, middle_x, 0.6, true); // moderately punish point far away from the x-middle. // keep track of the best point if (quality>bestquality) { bestquality=quality; bestquality_max=lastmax; bestquality_j=(j+j0)/2; bestquality_width=j-j0; } // irrelevant drawing stuff int x=drawing.cols-drawing.cols*((j+j0)/2-first_nonbottom_idx)/(contour.size()-first_nonbottom_idx); line(drawing, Point(x,25+40-3*quality), Point(x, 25+40), Scalar(0,255,0)); circle(drawing, contour[(j+j0)/2], 1, Scalar(128,0,0)); } lastmax=-999999; // reset lastmax, so the search can go on. TODO: ugly. } } // now bestquality_j holds the index of the point with the best quality or -1 upon failure *bestquality_out = bestquality; *bestquality_max_out = bestquality_max; *bestquality_j_out = bestquality_j; *bestquality_width_out = bestquality_width; return bestquality_j; } // returns index of ideal steering point or -1 on failure int HorizonSteerer::find_ideal_line(vector& contour, Point origin_point, int** contour_map, int bestquality_j) // TODO: this code is crappy, slow, and uses brute force. did i mention it's crappy and slow? { assert(bestquality_j>=0 && bestquality_j=0; xx--) { int intersection2 = find_intersection_index(origin_point.x, origin_point.y, xx, contour[bestquality_j].y, contour_map, false); if (intersection2<0) // won't happen anyway { cout << "SHOULD NOT HAPPEN: no intersection" << endl; break; } else if (intersection2>=bestquality_j) // now we intersect the opposite (=left) border { if (contour[intersection2].y>=lastheight) // we intersect at a lower = worse point? xx++; // then undo the last step break; } lastheight=contour[intersection2].y; } } else if (intersection > bestquality_j) // too far on the left == intersecting the left border { // rotate the line to the right till it gets better for (; xx=lastheight) // we intersect at a lower = worse point? xx--; // then undo the last step break; } lastheight=contour[intersection2].y; } } // else // we directly met the bestquality point, i.e. where we wanted to go to. // do nothing return find_intersection_index(origin_point.x,origin_point.y, xx, contour[bestquality_j].y, contour_map, false); } } void HorizonSteerer::draw_angles_and_contour(Mat drawing, vector< vector >& contours, const vector& hierarchy, int first_nonbottom_idx, vector& contour, double* angles, double* angle_derivative) { // Draw contours drawContours(drawing, contours, -1, Scalar(255,0,0), 1, 8, hierarchy); // draw the angles for (int j=first_nonbottom_idx; j >& contours, const vector& hierarchy, int first_nonbottom_idx, vector& contour, double* angles, double* angle_derivative, int bestquality_j, int bestquality_width, int bestquality, int steering_point, Point origin_point, double confidence) { draw_angles_and_contour(drawing, contours, hierarchy, first_nonbottom_idx, contour, angles, angle_derivative); // draw the point where the left touches the right road border circle(drawing, contour[bestquality_j], 3, Scalar(255,255,0)); circle(drawing, contour[bestquality_j], 2, Scalar(255,255,0)); circle(drawing, contour[bestquality_j], 1, Scalar(255,255,0)); circle(drawing, contour[bestquality_j], 0, Scalar(255,255,0)); // draw the detected left and right border. low saturation means // a worse detection result int antisaturation = 200-(200* bestquality/10.0); if (antisaturation<0) antisaturation=0; for (int j=0;j=0) // should be always true line(drawing, contour[steering_point], origin_point, Scalar(0,255,255)); rectangle(drawing, Point(0.25*xlen-2, 100-2), Point (0.75*xlen+2, 150+2), Scalar(255,255,255)); rectangle(drawing, Point(0.25*xlen, 100), Point ((0.25+0.5*confidence)*xlen, 150), Scalar(0,0,200*confidence+50), CV_FILLED); } #define SMOOTHEN_BOTTOM 20 #define SMOOTHEN_MIDDLE 7 #define ANG_SMOOTH 9 // return the point to steer to, or origin_point upon error Point HorizonSteerer::find_steering_point(Mat orig_img, Point origin_point, int** contour_map, Mat& drawing, double* confidence) // orig_img is a binary image with only one region // confidence is between 0.0 (not sure at all) and 1.0 (definitely sure) { assert(confidence!=NULL); assert(contour_map!=NULL); Mat img; orig_img.copyTo(img); // this is needed because findContours destroys its input. drawing = Mat::zeros( img.size(), CV_8UC3 ); vector > contours; vector hierarchy; findContours(img, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_NONE, Point(0, 0)); assert(contours.size()>=1); int low_y, low_idx, high_y, first_nonbottom_idx; vector& contour = prepare_and_get_contour(contours, hierarchy, &low_y, &low_idx, &high_y, &first_nonbottom_idx); if (! (first_nonbottom_idx find_bestquality_index(contour, angle_derivative, high_y, first_nonbottom_idx, drawing, &bestquality_j, &bestquality_width, &bestquality, &bestquality_max)) { draw_angles_and_contour(drawing, contours, hierarchy, first_nonbottom_idx, contour, angles, angle_derivative); *confidence=0.0; delete [] angles; delete [] angle_derivative; return origin_point; } // now we have a naive steering point. the way to it might lead // us offroad, however. int steering_point=find_ideal_line(contour, origin_point, contour_map, bestquality_j); *confidence = (bestquality-1.0) / 7.0; if (*confidence<0.0) *confidence=0; if (*confidence>1.0) *confidence=1.0; draw_it_all(drawing, contours, hierarchy, first_nonbottom_idx, contour, angles, angle_derivative,bestquality_j,bestquality_width,bestquality_max,steering_point, origin_point, *confidence); cout << bestquality << "\t" << bestquality_max<=0) { return contour[steering_point]; } else { *confidence=0.0; return origin_point; } }