Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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();
}
@saikrishna-1996

This comment has been minimized.

Copy link

@saikrishna-1996 saikrishna-1996 commented Jan 6, 2017

I think this code is not compatible with OpenCV2.4.11 or higher versions. Any updates on this?

@adiv5

This comment has been minimized.

Copy link

@adiv5 adiv5 commented Jun 25, 2021

Is there any way to get this implemented in python opencv? Also if yes then can the details for it be explained?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment