Skip to content

Instantly share code, notes, and snippets.

@blooberr
Forked from tanmaykm/cv_utils.cpp
Created June 8, 2016 17:12
Show Gist options
  • Save blooberr/7f441b81aa3218ebbe31f1ece49885fc to your computer and use it in GitHub Desktop.
Save blooberr/7f441b81aa3218ebbe31f1ece49885fc to your computer and use it in GitHub Desktop.
Separating items in store shelves using OpenCV.
/*
* cv_utils.cpp
*
* Created on: Dec 7, 2012
* Author: tan
* Related blog post at:
* http://sidekick.windforwings.com/2012/12/opencv-separating-items-on-store-shelves.html
*
*/
#include <opencv/cv.h>
#include <opencv/highgui.h>
#include <math.h>
#include <stdlib.h>
#include "cv_utils.h"
/*****************************************************
* print what optimization libraries are available
*****************************************************/
void print_lib_version() {
const char* libraries;
const char* modules;
cvGetModuleInfo(NULL, &libraries, &modules);
printf("Libraries: %s\nModules: %s\n", libraries, modules);
}
/*****************************************************
* comparison functions used for sorting lines and points
*****************************************************/
int compare_line_y_then_x(const void *_l1, const void *_l2, void* userdata) {
CvPoint* l1 = (CvPoint*)_l1;
CvPoint* l2 = (CvPoint*)_l2;
int y_diff = int(l1[0].y - l2[0].y);
if(0 != y_diff) return y_diff;
int x_diff = int(l1[0].x - l2[0].x);
return x_diff;
}
int compare_line_x_then_y(const void *_l1, const void *_l2, void* userdata) {
CvPoint* l1 = (CvPoint*)_l1;
CvPoint* l2 = (CvPoint*)_l2;
int x_diff = int(l1[0].x - l2[0].x);
if(0 != x_diff) return x_diff;
int y_diff = int(l1[0].y - l2[0].y);
return y_diff;
}
int compare_int(const void *i1, const void *i2) {
int diff = *((int *)i1) - *((int *)i2);
return diff;
}
/*****************************************************
* convert lines from rho-theta to x-y space
*****************************************************/
CvPoint * rho_theta_to_line(float *rho_theta, CvPoint *line, int max_dim) {
float rho = rho_theta[0];
float theta = rho_theta[1];
double a = cos(theta), b = sin(theta);
double x0 = a * rho, y0 = b * rho;
line[0].x = cvRound(x0 + max_dim * (-b));
line[0].y = cvRound(y0 + max_dim * (a));
line[1].x = cvRound(x0 - max_dim * (-b));
line[1].y = cvRound(y0 - max_dim * (a));
if(line[0].x < 0) line[0].x = 0;
if(line[0].y < 0) line[0].y = 0;
if(line[1].x < 0) line[1].x = 0;
if(line[1].y < 0) line[1].y = 0;
return line;
}
/*****************************************************
* from a sequence of lines, select only lines
* that match certain conditions
*****************************************************/
CvSeq* select_lines_by_separation(CvSeq *ret, int sep, float accuracy, bool is_y) {
CvPoint* line1 = (CvPoint*)cvGetSeqElem(ret, 0);
for(int line_idx = 1; line_idx < ret->total; line_idx++) {
CvPoint* line2 = (CvPoint*)cvGetSeqElem(ret, line_idx);
int this_dist = is_y ? abs(line1[0].y - line2[0].y) : abs(line1[0].x - line2[0].x);
if(this_dist < (accuracy*sep)) {
//printf("Ignoring line %d. Less than sep [%d]\n", line_idx, this_dist);
cvSeqRemove(ret, line_idx);
line_idx--;
}
else {
//printf("Keeping line %d. More than sep [%d]\n", line_idx, this_dist);
line1 = line2;
}
}
return ret;
}
CvSeq* select_lines_with_angle(CvSeq *ret, double angle, double accuracy) {
assert(angle < CV_PI);
assert(accuracy < (CV_PI/8));
for(int line_idx = 0; line_idx < ret->total; line_idx++) {
CvPoint* line = (CvPoint*)cvGetSeqElem(ret, line_idx);
double y_dist = fabs(line[1].y - line[0].y) + 1;
double x_dist = fabs(line[1].x - line[0].x) + 1;
double theta = atan(y_dist/x_dist);
// consider only from 0 to PI
if(theta >= CV_PI) theta -= CV_PI;
if(fabs(theta - angle) > accuracy) {
/*
printf("removing line %d,%d->%d,%d with angle [%f] [%f] angle[%f] accuracy[%f]\n",
line[0].x, line[0].y, line[1].x, line[1].y,
theta, theta * 180 / CV_PI, angle, accuracy);
*/
cvSeqRemove(ret, line_idx);
line_idx--;
}
/*else {
printf("keeping line %d,%d->%d,%d with angle [%f] [%f] angle[%f] accuracy[%f]\n",
line[0].x, line[0].y, line[1].x, line[1].y,
theta, theta * 180 / CV_PI, angle, accuracy);
}*/
}
return ret;
}
int get_median_dist(int *dist_arr, int dist_arr_cnt) {
if(0 == dist_arr_cnt) return 0;
// sort the distances
qsort(dist_arr, dist_arr_cnt, sizeof(int), compare_int);
/*
printf("Sorted distances: ");
for(int line_idx=0; line_idx < dist_arr_cnt; line_idx++) printf("%d, ", dist_arr[line_idx]);
printf("\n");
*/
return dist_arr[(dist_arr_cnt-1)/2];
}
double avg_line_dist(CvSeq *lines, bool is_y) {
double dist = 0;
CvPoint* line1 = (CvPoint*)cvGetSeqElem(lines, 0);
for(int line_idx = 1; line_idx < lines->total; line_idx++) {
CvPoint* line2 = (CvPoint*)cvGetSeqElem(lines, line_idx);
if(is_y) dist += abs(line1[0].y - line2[0].y);
else dist += abs(line1[0].x - line2[0].x);
line1 = line2;
}
return (dist / lines->total);
}
void print_lines(CvSeq *lines) {
for(int line_idx = 0; line_idx < lines->total; line_idx++) {
CvPoint* line = (CvPoint*)cvGetSeqElem(lines, line_idx);
printf("line %d,%d->%d,%d\n", line[0].x, line[0].y, line[1].x, line[1].y);
}
}
void draw_lines(CvSeq* lines, IplImage *img_color_dst, int col_idx) {
static CvScalar colors[LINE_DRAW_NUM_COLORS] = { CV_RGB(255,0,0), CV_RGB(0,255,0), CV_RGB(0,0,255) };
for(int line_idx = 0; line_idx < lines->total; line_idx++) {
CvPoint* line = (CvPoint*)cvGetSeqElem(lines, line_idx);
cvLine(img_color_dst, line[0], line[1], colors[col_idx], 2, CV_AA);
}
}
CvSeq * find_hough_lines(IplImage *img, CvMemStorage *mem_store, int hough_method, double rho, double theta, int threshold, double p1, double p2, int max_dim) {
CvSeq *lines = cvHoughLines2(img, mem_store, hough_method, rho, theta, threshold, p1, p2);
CvSeq *ret = NULL;
if((0 == hough_method) || (1 == hough_method)) {
ret = cvCreateSeq(0, sizeof(CvSeq), sizeof(CvPoint)*2, mem_store);
for(int line_idx = 0; line_idx < lines->total; line_idx++) {
CvPoint line_ends[2];
cvSeqPush(ret, rho_theta_to_line((float *)cvGetSeqElem(lines, line_idx), line_ends, max_dim));
}
}
else {
ret = lines;
}
return ret;
}
/*****************************************************
* smoothen the image and adjust brightness and contrast
*****************************************************/
void smooth_brightness_contrast(IplImage *img, IplImage *img_result, int brightness, int contrast) {
cvSmooth(img, img_result, CV_BILATERAL, 5, 5, 30, 30);
// increase contrast and adjust brightness
cvAddWeighted(img_result, 0.5, img_result, 0.5, brightness, img_result);
// increase contrast further if specified
for(int contrast_idx = 0; contrast_idx < contrast; contrast_idx++) {
cvAddWeighted(img_result, 1, img_result, 1, 0, img_result);
}
}
/*
* cv_utils.h
*
* Created on: Dec 7, 2012
* Author: tan
* Related blog post at:
* http://sidekick.windforwings.com/2012/12/opencv-separating-items-on-store-shelves.html
*/
#ifndef CV_UTILS_H_
#define CV_UTILS_H_
#define LINE_DRAW_NUM_COLORS 3
void print_lib_version();
int compare_line_y_then_x(const void *_l1, const void *_l2, void* userdata);
int compare_line_x_then_y(const void *_l1, const void *_l2, void* userdata);
int compare_int(const void *i1, const void *i2);
double avg_line_dist(CvSeq *lines, bool is_y);
int get_median_dist(int *dist_arr, int dist_arr_cnt);
CvPoint * rho_theta_to_line(float *rho_theta, CvPoint *line, int max_dim);
CvSeq* select_lines_with_angle(CvSeq *ret, double angle, double accuracy);
CvSeq* select_lines_by_separation(CvSeq *ret, int sep, float accuracy, bool is_y);
void print_lines(CvSeq *lines);
void smooth_brightness_contrast(IplImage *img, IplImage *img_result, int param_brightness_factor, int param_contrast_factor);
void draw_lines(CvSeq* lines, IplImage *img_color_dst, int col_idx);
CvSeq * find_hough_lines(IplImage *img, CvMemStorage *mem_store, int hough_method, double rho, double theta, int threshold, double p1, double p2, int max_dim);
#endif /* CV_UTILS_H_ */
/*
* rack_detect.cpp
*
* Created on: Dec 7, 2012
* Author: tan
* Related blog post at:
* http://sidekick.windforwings.com/2012/12/opencv-separating-items-on-store-shelves.html
*
*/
#include <opencv/cv.h>
#include <opencv/highgui.h>
#include <math.h>
#include <stdlib.h>
#include "cv_utils.h"
#define MAX_SHELVES 7
#define MAX_ITEMS_PER_ROW 30
const char *WINDOW_NAME = "Store Item Detect";
const int HOUGH_METHODS[3] = { CV_HOUGH_STANDARD, CV_HOUGH_MULTI_SCALE, CV_HOUGH_PROBABILISTIC };
int param_disp_image = 5; // the image to be displayed in the debug window
int param_hough_method = 0; // the hough method to use. right now fixed to CV_HOUGH_STANDARD
int param_h_rho = 0; // pixels granularity for rho/distance (=param+1)
int param_h_theta = 71; // angle granularity for theta (=2*pi/(param+1))
int param_h_threshold = 75; // number of points that should support the line (used as param+1 %)
int param_v_rho = 0; // pixels granularity for rho/distance (=param+1)
int param_v_theta = 71; // angle granularity for theta (=2*pi/(param+1))
int param_v_threshold = 50; // number of points that should support the line (used as param+1 %)
int param_p1 = 0; // increase accuracy of rho by factor
int param_p2 = 1000; // increase accuracy of theta by factor
int param_contrast_factor = 0; // increase contrast by factor
int param_brightness_factor = 50; // increase brightness by factor (50 is no change)
// image attributes kept as globals and used at different places
int img_attrib_depth;
int img_attrib_channels;
CvSize img_attrib_dim;
IplImage *img_read;
IplImage *img_src;
IplImage *img_smooth;
IplImage *img_edges;
IplImage *img_h_edge_src;
IplImage *img_v_edge_src;
IplImage *img_color_dst;
CvMemStorage *mem_store;
CvMat *h_smudge_kernel;
CvMat *v_smudge_kernel;
CvTrackbarCallback on_change CV_DEFAULT(NULL);
bool param_changed = true;
void on_param_change(int pos) {
param_changed = true;
}
void create_gui() {
cvNamedWindow(WINDOW_NAME);
cvCreateTrackbar("Brightness", WINDOW_NAME, &param_brightness_factor, 100, on_param_change);
cvCreateTrackbar("Contrast", WINDOW_NAME, &param_contrast_factor, 10, on_param_change);
cvCreateTrackbar("Display Image", WINDOW_NAME, &param_disp_image, 5, on_param_change);
// threshold is effectively the number of points supporting the line
// we start at 25% of diagonals
cvCreateTrackbar("H Threshold", WINDOW_NAME, &param_h_threshold, 100, on_param_change);
cvCreateTrackbar("V Threshold", WINDOW_NAME, &param_v_threshold, 100, on_param_change);
img_edges = cvCreateImage(img_attrib_dim, 8, 1);
img_h_edge_src = cvCreateImage(img_attrib_dim, 8, 1);
img_v_edge_src = cvCreateImage(img_attrib_dim, 8, 1);
img_color_dst = cvCreateImage(img_attrib_dim, 8, 3);
mem_store = cvCreateMemStorage(0);
h_smudge_kernel = cvCreateMat(5, 5, CV_32F);
cvSet(h_smudge_kernel, cvRealScalar(0));
for(int col_idx=0; col_idx < 5; col_idx++) {
cvSet2D(h_smudge_kernel, 1, col_idx, cvRealScalar(1.0/4));
}
cvSet2D(h_smudge_kernel, 2, 2, cvRealScalar(0));
v_smudge_kernel = cvCreateMat(5, 5, CV_32F);
cvSet(v_smudge_kernel, cvRealScalar(0));
for(int row_idx=0; row_idx < 5; row_idx++) {
cvSet2D(v_smudge_kernel, row_idx, 1, cvRealScalar(1.0/4));
}
cvSet2D(v_smudge_kernel, 2, 2, cvRealScalar(0));
}
void destroy_gui() {
cvDestroyWindow(WINDOW_NAME);
cvReleaseImage(&img_read);
cvReleaseImage(&img_src);
cvReleaseImage(&img_smooth);
cvReleaseImage(&img_edges);
cvReleaseImage(&img_h_edge_src);
cvReleaseImage(&img_v_edge_src);
cvReleaseImage(&img_color_dst);
cvReleaseMemStorage(&mem_store);
cvReleaseMat(&h_smudge_kernel);
cvReleaseMat(&v_smudge_kernel);
}
CvSeq* trim_spurious_v_lines(CvSeq *ret, int max_x, int max_shelves=MAX_ITEMS_PER_ROW) {
if(ret->total <= 2) return ret; // if we have just 2 lines or less, we can't process anything
cvSeqSort(ret, compare_line_x_then_y, NULL); // sort the lines so that we can calculate distance between them
// remove close by lines as they are most probably spurious
// find average y-distance
// if y-dist is less than half of avg y-dist, put a line at the center of the two, remove one
int dist_arr_cnt = 0;
int *dist_arr = (int *)alloca((ret->total+1) * sizeof(int));
// compare first line against edge of image
CvPoint* first_line = (CvPoint*) cvGetSeqElem(ret, 0);
if(first_line[0].x > max_x/max_shelves) dist_arr[dist_arr_cnt++] = first_line[0].x;
for(int line_idx = 1; line_idx < ret->total; line_idx++) {
CvPoint* line1 = (CvPoint*)cvGetSeqElem(ret, line_idx-1);
CvPoint* line2 = (CvPoint*)cvGetSeqElem(ret, line_idx);
int diff_dist = abs(line1[0].x - line2[0].x);
if(diff_dist < max_x/max_shelves) {
//printf("Ignoring line with distance %d\n", diff_dist);
continue;
}
dist_arr[dist_arr_cnt++] = diff_dist;
/*
printf("diff %d. line1 %d,%d->%d,%d, line2 %d,%d->%d,%d\n", diff_dist,
line1[0].x, line1[0].y, line1[1].x, line1[1].y,
line2[0].x, line2[0].y, line2[1].x, line2[1].y);
*/
}
// compare last line against edge of image
CvPoint* last_line = (CvPoint*)cvGetSeqElem(ret, ret->total-1);
if((max_x - last_line[0].x) > max_x/max_shelves) dist_arr[dist_arr_cnt++] = max_x - last_line[0].x;
int x_median_dist = get_median_dist(dist_arr, dist_arr_cnt);
//printf("Median vert shelve distance before trimming: %d. Considered: %d of %d lines\n", x_median_dist, dist_arr_cnt, ret->total);
select_lines_by_separation(ret, x_median_dist, 0.75, false);
//printf("Average vert shelve distance after trimming: %d. Num lines: %d\n", (int)avg_line_dist(ret, false), ret->total);
return ret;
}
CvSeq* trim_spurious_h_lines(CvSeq *ret, int max_y, int max_shelves=MAX_SHELVES) {
if(ret->total <= 2) return ret;
cvSeqSort(ret, compare_line_y_then_x, NULL);
// remove close by lines as they are most probably spurious
// find average y-distance
// if y-dist is less than half of avg y-dist, put a line at the center of the two, remove one
int dist_arr_cnt = 0;
int *dist_arr = (int *)alloca((ret->total+1) * sizeof(int));
CvPoint* first_line = (CvPoint*)cvGetSeqElem(ret, 0);
if(first_line[0].y > max_y/max_shelves) {
dist_arr[dist_arr_cnt++] = first_line[0].y;
}
for(int line_idx = 1; line_idx < ret->total; line_idx++) {
CvPoint* line1 = (CvPoint*)cvGetSeqElem(ret, line_idx-1);
CvPoint* line2 = (CvPoint*)cvGetSeqElem(ret, line_idx);
int diff_dist = abs(line1[0].y - line2[0].y);
if(diff_dist < max_y/max_shelves) {
//printf("Ignoring line with distance %d\n", diff_dist);
continue;
}
dist_arr[dist_arr_cnt++] = diff_dist;
/*
printf("diff %d. line1 %d,%d->%d,%d, line2 %d,%d->%d,%d\n", diff_dist,
line1[0].x, line1[0].y, line1[1].x, line1[1].y,
line2[0].x, line2[0].y, line2[1].x, line2[1].y);
*/
}
CvPoint* last_line = (CvPoint*)cvGetSeqElem(ret, ret->total-1);
if((max_y - last_line[0].y) > max_y/max_shelves) {
dist_arr[dist_arr_cnt++] = max_y - last_line[0].y;
}
int y_median_dist = get_median_dist(dist_arr, dist_arr_cnt);
//printf("Median horz shelve distance before trimming: %d. Considered: %d of %d lines\n", y_median_dist, dist_arr_cnt, ret->total);
select_lines_by_separation(ret, y_median_dist, 0.75, true);
//printf("Average horz shelve distance after trimming: %d. Num lines: %d\n", avg_line_dist(ret, true), ret->total);
return ret;
}
void process_h_v_edges() {
// horizontal edge detect
cvCvtColor(img_smooth, img_edges, CV_BGR2GRAY);
cvLaplace(img_edges, img_edges, 3);
cvFilter2D(img_edges, img_h_edge_src, h_smudge_kernel);
cvThreshold(img_h_edge_src, img_h_edge_src, 200, 255, CV_THRESH_BINARY);
// vertical edge detect
cvFilter2D(img_edges, img_v_edge_src, v_smudge_kernel);
cvThreshold(img_v_edge_src, img_v_edge_src, 200, 255, CV_THRESH_BINARY);
}
CvSeq* find_shelve_horz_lines(int width, int height) {
CvSeq *ret = find_hough_lines(img_h_edge_src, mem_store, HOUGH_METHODS[param_hough_method],
param_h_rho+1,
2*CV_PI/(param_h_theta+1),
width * (param_h_threshold+1) / 100,
param_p1, param_p2, width);
//printf("num horz lines before angle trim %d\n", ret->total);
ret = select_lines_with_angle(ret, 0, CV_PI/18);
//printf("num horz lines after angle trim %d\n", ret->total);
// add a line for top and one for bottom
CvPoint line_ends[2];
line_ends[0].x = -width;
line_ends[1].x = width;
line_ends[0].y = line_ends[1].y = 0;
cvSeqPush(ret, line_ends);
line_ends[0].y = line_ends[1].y = height;
cvSeqPush(ret, line_ends);
int tot_before, tot_after;
do {
tot_before = ret->total;
ret = trim_spurious_h_lines(ret, height);
tot_after = ret->total;
} while(tot_before != tot_after);
printf("num horz lines: %d\n", ret->total);
return ret;
}
CvSeq* find_shelve_vert_lines(int width, int height) {
CvSeq *ret = find_hough_lines(img_v_edge_src, mem_store, HOUGH_METHODS[param_hough_method],
param_v_rho+1,
2*CV_PI/(param_v_theta+1),
height * (param_v_threshold+1) / 100,
param_p1, param_p2, height);
//printf("num vert lines before angle trim %d\n", ret->total);
ret = select_lines_with_angle(ret, CV_PI/2, CV_PI/72);
//printf("num vert lines after angle trim %d\n", ret->total);
// add a line for left and one for right
CvPoint line_ends[2];
line_ends[0].y = -height;
line_ends[1].y = height;
line_ends[0].x = line_ends[1].x = 0;
cvSeqPush(ret, line_ends);
line_ends[0].x = line_ends[1].x = width;
cvSeqPush(ret, line_ends);
int tot_before, tot_after;
do {
tot_before = ret->total;
ret = trim_spurious_v_lines(ret, width);
tot_after = ret->total;
} while(tot_before != tot_after);
printf("num vert lines: %d\n", ret->total);
//print_lines(ret);
return ret;
}
/*****************************************************
* limit max size of image to be processed to 800x600
*****************************************************/
void get_approp_size(IplImage *input, IplImage **output, CvSize &img_size, int &img_depth, int &img_channels) {
CvSize ori_size = cvGetSize(input);
img_depth = input->depth;
img_channels = input->nChannels;
printf("Image size: %d x %d\nDepth: %d\nChannels: %d\n", ori_size.width, ori_size.height, img_depth, img_channels);
float div_frac_h = 600.0/((float)ori_size.height);
float div_frac_w = 800.0/((float)ori_size.width);
float div_frac = div_frac_w < div_frac_h ? div_frac_w : div_frac_h;
if(div_frac > 1) div_frac = 1;
img_size.height = ori_size.height * div_frac;
img_size.width = ori_size.width * div_frac;
*output = cvCreateImage(img_size, img_depth, img_channels);
cvResize(input, *output);
}
int main(int argc, char** argv) {
if(argc < 2) {
printf("Usage: %s <image>\n", argv[0]);
return 1;
}
img_read = cvLoadImage(argv[1]);
if(NULL == img_read) {
printf("Error loading image %s\n", argv[1]);
return -1;
}
get_approp_size(img_read, &img_src, img_attrib_dim, img_attrib_depth, img_attrib_channels);
create_gui();
int col_idx = 0;
img_smooth = cvCreateImage(img_attrib_dim, img_attrib_depth, img_attrib_channels);
while(true) {
if(param_changed) {
param_changed = false;
smooth_brightness_contrast(img_src, img_smooth, param_brightness_factor-50, param_contrast_factor);
cvClearMemStorage(mem_store);
process_h_v_edges();
cvCopy(img_src, img_color_dst);
CvSeq* lines = find_shelve_horz_lines(img_attrib_dim.width, img_attrib_dim.height);
// rotate color for the lines
if(LINE_DRAW_NUM_COLORS == (++col_idx)) col_idx = 0;
// for each image segment, find vertical lines
CvRect roi_rect;
roi_rect.x = 0;
roi_rect.y = 0;
roi_rect.width = img_attrib_dim.width;
int max_y = img_attrib_dim.height;
for(int line_idx = 0; line_idx < lines->total; line_idx++) {
CvPoint* line = (CvPoint*)cvGetSeqElem(lines, line_idx);
// skip if the line seems to be the top or bottom border
if(line[0].y < max_y/MAX_SHELVES) {
//printf("skipping beginning line with y[%d] max_y[%d]\n", line[0].y, max_y);
continue;
}
roi_rect.height = line[0].y - roi_rect.y;
cvSetImageROI(img_v_edge_src, roi_rect);
// process
CvSeq* v_lines = find_shelve_vert_lines(roi_rect.width, roi_rect.height);
for(int line_idx = 0; line_idx < v_lines->total; line_idx++) {
CvPoint* one_v_line = (CvPoint*)cvGetSeqElem(v_lines, line_idx);
one_v_line[0].y = roi_rect.y;
one_v_line[1].y = roi_rect.y + roi_rect.height;
}
draw_lines(v_lines, img_color_dst, col_idx);
// prepare rect for the next slot
roi_rect.y += roi_rect.height;
cvResetImageROI(img_v_edge_src);
}
draw_lines(lines, img_color_dst, col_idx);
if(0 == param_disp_image) cvShowImage(WINDOW_NAME, img_src);
else if(1 == param_disp_image) cvShowImage(WINDOW_NAME, img_smooth);
else if(2 == param_disp_image) cvShowImage(WINDOW_NAME, img_edges);
else if(3 == param_disp_image) cvShowImage(WINDOW_NAME, img_h_edge_src);
else if(4 == param_disp_image) cvShowImage(WINDOW_NAME, img_v_edge_src);
else if(5 == param_disp_image) cvShowImage(WINDOW_NAME, img_color_dst);
}
char c = cvWaitKey(500);
if(27 == c) break;
}
destroy_gui();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment