Distance measurement between a robot and the object is needed to control the action of the robot such as grabbing an object or even avoiding obstacles. There are many methods to estimate the distance such as ultrasonic ranging, laser ranging, and vision based ranging. Vision based techniques have the merit of its low cost, so in this post, we will learn a method for distance measurement between a pinhole camera and a circle marker and implement it.
The pinhole (monocular) camera generates a one-to-one relationship between the object and the image. Using this principle, we can deduce a relationship between known parameters: focal length (f), radius of marker in the image plane (r), and radios of marker in the object plane (R) and unknown parameter, distance from the camera to the object(d).
Using the principle of Similar Triangles, we can obtain the formulas as follows:
OpenCV can be used to estimate the focal length after taking 10 to 20 photos of checkerboard pattern with your camera like here, the result will be intrinsic parameters: focal length and optical center and extrinsic parameters: rotation and translation vectors of the camera.
But we will use another scenario as follow:
We will use Calibration class to estimate the focal length in pixels and then use it in our real-time app to measure the distance, as follow:
FrameGrabber grabber = FrameGrabber.createDefault(0);
grabber.start();
Frame frame = grabber.grab();
System.out.println("\nFrame size " + frame.imageWidth + " x " + frame.imageHeight);
IplImage grabbedImage = converter.convert(frame);
Mat img = converter.convertToMat(frame);
displayImage(grabbedImage, "Grabbed Image");
/*Resize Image (comment this section if you want to use default resolution)*/
Size size = new Size(640, 640);
resize(img, img, size);
grabbedImage = new IplImage(img);
/*Invert to HSV color space*/
Mat imghsv = new Mat();
cvtColor(img, imghsv, COLOR_BGR2HSV);
//*Convert image to grayscale*/
IplImage gray = cvCreateImage(cvGetSize(grabbedImage), IPL_DEPTH_8U, 1);
cvCvtColor(grabbedImage, gray, CV_BGR2GRAY);
/*Binarising Image*/
IplImage binimg = cvCreateImage(cvGetSize(gray), IPL_DEPTH_8U, 1);
cvThreshold(gray, binimg, 0, 255, THRESH_BINARY + CV_THRESH_OTSU);
imwrite("binarise.jpg", new Mat(binimg));
/*Find countour */
CvMemStorage storage = cvCreateMemStorage(0);
CvSeq contours = new CvSeq();
cvFindContours(binimg, storage, contours, Loader.sizeof(CvContour.class), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0));
CvSeq ptr = new CvSeq();
Mat m = new Mat(grabbedImage);
for (ptr = contours; ptr != null; ptr = ptr.h_next()) {
/*Find Enclosing Circles*/
Point2f center = new Point2f();
FloatPointer radius = new FloatPointer(1f);
opencv_imgproc.minEnclosingCircle(new Mat(ptr), center, radius);
/*Check, if the color of the center picel is blue, it is our marker*/
if (isPixelBlue(imghsv, (int) center.x(), (int) center.y())) {
double area = opencv_imgproc.contourArea(new Mat(ptr), true);
if (area > 100) {
opencv_imgproc.cvCircle(grabbedImage, new CvPoint((int) center.x(), (int) center.y()), (int) radius.get(0),
CV_RGB(0, 255, 0), 3, 0, 0);
opencv_imgproc.putText(m, area + "", new Point((int) center.x(), (int) center.y()), 0, 2.0, new Scalar(0, 0, 0, 0));//print result above every digit
float rad = radius.get(0);
System.out.println("Radius " + rad);
double focallen = (DISTANCE_TO_OBJECT * rad) / RADIUS_OF_MARKER;
System.out.println("Focal lenght is " + focallen + " Pixels");
}
}
}//End for countors
The code in DistanceMeasurement class is the same as Calibration class except we add a while loop to make it in real-time and modify the logic code for computing the distance. After segmenting the marker, we compute its radius in the image plane and substitute along with -known- the focal length and the radius of the marker (1cm) in equation \eqref{eq:equ3} to get the estimated distance between the camera and object in cm.
/*Find countour */
CvMemStorage storage = cvCreateMemStorage(0);
CvSeq contours = new CvSeq();
cvFindContours(binimg, storage, contours, Loader.sizeof(CvContour.class), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0));
CvSeq ptr = new CvSeq();
Mat m = new Mat(grabbedImage);
for (ptr = contours; ptr != null; ptr = ptr.h_next()) {
/*Find Enclosing Circles*/
Point2f center = new Point2f();
FloatPointer radius = new FloatPointer(1f);
opencv_imgproc.minEnclosingCircle(new Mat(ptr), center, radius);
/*Check, if the color of the center picel is blue, it is our marker*/
if (isPixelBlue(imghsv, (int) center.x(), (int) center.y())) {
double area = contourArea(new Mat(ptr), true);
if (area > 100) {
cvCircle(grabbedImage, new CvPoint((int) center.x(), (int) center.y()), (int) radius.get(0),
CV_RGB(0, 255, 0), 3, 0, 0);
float rad = radius.get(0);
System.out.println("Radius is " + rad);
double distance = (FOCAL_LENGTH * RADIUS_OF_MARKER) / rad;
System.out.println("Distance in cm " + distance);
putText(m, "Distance is : " + distance + " cm", new Point(200, 100), 0, 0.5, new Scalar(0, 255, 0, 0));
}
}
}//End for countors
canvas.showImage(converter.convert(m));
Thread.sleep(150);
}//End if