Previous tutorial: Convex hull
Next tutorial: Creating rotated bounding boxes and ellipses for contours
Original author | Ana Huamán |
---|---|
Compatibility | OpenCV >= 3.0 |
Goals
In this tutorial you will learn how to
- Use the OpenCV function cv::boundingRect
- Use the OpenCV function cv::minEnclosingCircle
Code
C++
The tutorial code is shown below. You can also download it from here
#include "opencv2/imgcodecs.hpp" #include "opencv2/highgui.hpp" #include "opencv2/imgproc.hpp" #include <iostream> using namespace cv; using namespace std; Mat src_gray; int thresh = 100; RNG rng(12345); void thresh_callback(int, void* ); int main(int argc, char** argv) {<!-- --> CommandLineParser parser( argc, argv, "{@input | stuff.jpg | input image}" ); Mat src = imread( samples::findFile( parser.get<String>( "@input" ) ) ); if( src.empty() ) {<!-- --> cout << "Could not open or find the image!\\ " << endl; cout << "usage: " << argv[0] << " <Input image>" << endl; return -1; } cvtColor( src, src_gray, COLOR_BGR2GRAY ); blur( src_gray, src_gray, Size(3,3) ); const char* source_window = "Source"; namedWindow( source_window ); imshow( source_window, src ); const int max_thresh = 255; createTrackbar( "Canny thresh:", source_window, & amp;thresh, max_thresh, thresh_callback ); thresh_callback(0, 0); waitKey(); return 0; } void thresh_callback(int, void* ) {<!-- --> Mat canny_output; Canny(src_gray, canny_output, thresh, thresh*2); vector<vector<Point> > contours; findContours( canny_output, contours, RETR_TREE, CHAIN_APPROX_SIMPLE ); vector<vector<Point> > contours_poly( contours.size() ); vector<Rect> boundRect( contours.size() ); vector<Point2f>centers( contours.size() ); vector<float>radius( contours.size() ); for( size_t i = 0; i < contours.size(); i + + ) {<!-- --> approxPolyDP( contours[i], contours_poly[i], 3, true ); boundRect[i] = boundingRect( contours_poly[i] ); minEnclosingCircle( contours_poly[i], centers[i], radius[i] ); } Mat drawing = Mat::zeros( canny_output.size(), CV_8UC3 ); for( size_t i = 0; i< contours.size(); i + + ) {<!-- --> Scalar color = Scalar( rng.uniform(0, 256), rng.uniform(0,256), rng.uniform(0,256) ); drawContours( drawing, contours_poly, (int)i, color ); rectangle( drawing, boundRect[i].tl(), boundRect[i].br(), color, 2 ); circle(drawing, centers[i], (int)radius[i], color, 2); } imshow( "Contours", drawing ); }
Java
The tutorial code is shown below. You can also download it from here
import java.awt.BorderLayout; import java.awt.Container; import java.awt.Image; import java.util.ArrayList; import java.util.List; import java.util.Random; import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSlider; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.opencv.core.Core; import org.opencv.core.CvType; import org.opencv.core.Mat; import org.opencv.core.MatOfPoint; import org.opencv.core.MatOfPoint2f; import org.opencv.core.Point; import org.opencv.core.Rect; import org.opencv.core.Scalar; import org.opencv.core.Size; import org.opencv.highgui.HighGui; import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgproc.Imgproc; class GeneralContours1 {<!-- --> private Mat srcGray = new Mat(); private JFrame frame; private JLabel imgSrcLabel; private JLabel imgContoursLabel; private static final int MAX_THRESHOLD = 255; private int threshold = 100; private Random rng = new Random(12345); public GeneralContours1(String[] args) {<!-- --> String filename = args.length > 0 ? args[0] : "../data/stuff.jpg"; Mat src = Imgcodecs.imread(filename); if (src.empty()) {<!-- --> System.err.println("Cannot read image: " + filename); System.exit(0); } Imgproc.cvtColor(src, srcGray, Imgproc.COLOR_BGR2GRAY); Imgproc.blur(srcGray, srcGray, new Size(3, 3)); // Create and set up the window. frame = new JFrame("Creating Bounding boxes and circles for contours demo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Set up the content pane. Image img = HighGui.toBufferedImage(src); addComponentsToPane(frame.getContentPane(), img); // Use the content pane's default border layout. No need // setLayout(new BorderLayout()); //Show the window. frame.pack(); frame.setVisible(true); update(); } private void addComponentsToPane(Container pane, Image img) {<!-- --> if (!(pane.getLayout() instanceof BorderLayout)) {<!-- --> pane.add(new JLabel("Container doesn't use BorderLayout!")); return; } JPanel sliderPanel = new JPanel(); sliderPanel.setLayout(new BoxLayout(sliderPanel, BoxLayout.PAGE_AXIS)); sliderPanel.add(new JLabel("Canny threshold: ")); JSlider slider = new JSlider(0, MAX_THRESHOLD, threshold); slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(10); slider.setPaintTicks(true); slider.setPaintLabels(true); slider.addChangeListener(new ChangeListener() {<!-- --> @Override public void stateChanged(ChangeEvent e) {<!-- --> JSlider source = (JSlider) e.getSource(); threshold = source.getValue(); update(); } }); sliderPanel.add(slider); pane.add(sliderPanel, BorderLayout.PAGE_START); JPanel imgPanel = new JPanel(); imgSrcLabel = new JLabel(new ImageIcon(img)); imgPanel.add(imgSrcLabel); Mat blackImg = Mat.zeros(srcGray.size(), CvType.CV_8U); imgContoursLabel = new JLabel(new ImageIcon(HighGui.toBufferedImage(blackImg))); imgPanel.add(imgContoursLabel); pane.add(imgPanel, BorderLayout.CENTER); } private void update() {<!-- --> Mat cannyOutput = new Mat(); Imgproc.Canny(srcGray, cannyOutput, threshold, threshold * 2); List<MatOfPoint> contours = new ArrayList<>(); Mat hierarchy = new Mat(); Imgproc.findContours(cannyOutput, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE); MatOfPoint2f[] contoursPoly = new MatOfPoint2f[contours.size()]; Rect[] boundRect = new Rect[contours.size()]; Point[] centers = new Point[contours.size()]; float[][] radius = new float[contours.size()][1]; for (int i = 0; i < contours.size(); i + + ) {<!-- --> contoursPoly[i] = new MatOfPoint2f(); Imgproc.approxPolyDP(new MatOfPoint2f(contours.get(i).toArray()), contoursPoly[i], 3, true); boundRect[i] = Imgproc.boundingRect(new MatOfPoint(contoursPoly[i].toArray())); centers[i] = new Point(); Imgproc.minEnclosingCircle(contoursPoly[i], centers[i], radius[i]); } Mat drawing = Mat.zeros(cannyOutput.size(), CvType.CV_8UC3); List<MatOfPoint> contoursPolyList = new ArrayList<>(contoursPoly.length); for (MatOfPoint2f poly : contoursPoly) {<!-- --> contoursPolyList.add(new MatOfPoint(poly.toArray())); } for (int i = 0; i < contours.size(); i + + ) {<!-- --> Scalar color = new Scalar(rng.nextInt(256), rng.nextInt(256), rng.nextInt(256)); Imgproc.drawContours(drawing, contoursPolyList, i, color); Imgproc.rectangle(drawing, boundRect[i].tl(), boundRect[i].br(), color, 2); Imgproc.circle(drawing, centers[i], (int) radius[i][0], color, 2); } imgContoursLabel.setIcon(new ImageIcon(HighGui.toBufferedImage(drawing))); frame.repaint(); } } public class GeneralContoursDemo1 {<!-- --> public static void main(String[] args) {<!-- --> //Load the local OpenCV library System.loadLibrary(Core.NATIVE_LIBRARY_NAME); // Arrange tasks for the event dispatch thread: // Create and display the graphical user interface for this application. javax.swing.SwingUtilities.invokeLater(new Runnable() {<!-- --> @Override public void run() {<!-- --> new GeneralContours1(args); } }); } }
Python
The tutorial code is shown below. You can also download it from here
from __future__ import print_function import cv2 as cv import numpy as np import argparse import random as rng rng.seed(12345) def thresh_callback(val): threshold = val canny_output = cv.Canny(src_gray, threshold, threshold * 2) contours, _ = cv.findContours(canny_output, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) contours_poly = [None]*len(contours) boundRect = [None]*len(contours) centers = [None]*len(contours) radius = [None]*len(contours) for i, c in enumerate(contours): contours_poly[i] = cv.approxPolyDP(c, 3, True) boundRect[i] = cv.boundingRect(contours_poly[i]) centers[i], radius[i] = cv.minEnclosingCircle(contours_poly[i]) drawing = np.zeros((canny_output.shape[0], canny_output.shape[1], 3), dtype=np.uint8) for i in range(len(contours)): color = (rng.randint(0,256), rng.randint(0,256), rng.randint(0,256)) cv.drawContours(drawing, contours_poly, i, color) cv.rectangle(drawing, (int(boundRect[i][0]), int(boundRect[i][1])), \ (int(boundRect[i][0] + boundRect[i][2]), int(boundRect[i][1] + boundRect[i][3])), color, 2) cv.circle(drawing, (int(centers[i][0]), int(centers[i][1])), int(radius[i]), color, 2) cv.imshow('Contours', drawing) parser = argparse.ArgumentParser(description='Code for Creating Bounding boxes and circles for contours tutorial.') parser.add_argument('--input', help='Path to input image.', default='stuff.jpg') args = parser.parse_args() src = cv.imread(cv.samples.findFile(args.input)) if src is None: print('Could not open or find the image:', args.input) exit(0) # Convert image to gray and blur src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY) src_gray = cv.blur(src_gray, (3,3)) source_window = 'Source' cv.namedWindow(source_window) cv.imshow(source_window, src) max_thresh = 255 thresh = 100 # initial threshold cv.createTrackbar('Canny thresh:', source_window, thresh, max_thresh, thresh_callback) thresh_callback(thresh) cv.waitKey()
Description
The main function is very simple. As you can see from the comments, we do the following:
- Open the image, convert it to grayscale, and blur it to remove noise.
C++
CommandLineParser parser( argc, argv, "{@input | stuff.jpg | input image}" ); Mat src = imread( samples::findFile( parser.get<String>( "@input" ) ) ); if( src.empty() ) {<!-- --> cout << "Could not open or find the image!\\ " << endl; cout << "usage: " << argv[0] << " <Input image>" << endl; return -1; } cvtColor( src, src_gray, COLOR_BGR2GRAY ); blur( src_gray, src_gray, Size(3,3) );
Java
String filename = args.length > 0 ? args[0] : "../data/stuff.jpg"; Mat src = Imgcodecs.imread(filename); if (src.empty()) {<!-- --> System.err.println("Cannot read image: " + filename); System.exit(0); } Imgproc.cvtColor(src, srcGray, Imgproc.COLOR_BGR2GRAY); Imgproc.blur(srcGray, srcGray, new Size(3, 3));
Python
# Load source image parser = argparse.ArgumentParser(description='Code for Creating Bounding boxes and circles for contours tutorial.') parser.add_argument('--input', help='Path to input image.', default='stuff.jpg') args = parser.parse_args() src = cv.imread(cv.samples.findFile(args.input)) if src is None: print('Could not open or find the image:', args.input) exit(0) # Convert image to gray and blur it src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY) src_gray = cv.blur(src_gray, (3,3))
- Create a window titled “Source” and display the source file in it.
C++
const char* source_window = "Source"; namedWindow( source_window ); imshow( source_window, src );
Java
// Create and set up the window. frame = new JFrame("Creating Bounding boxes and circles for contours demo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Set up the content pane. Image img = HighGui.toBufferedImage(src); addComponentsToPane(frame.getContentPane(), img);
Python
# Create Window source_window = 'Source' cv.namedWindow(source_window) cv.imshow(source_window, src)
- Create a tracking bar on
source_window
and assign it a callback function. Generally speaking, callback functions are used to react to some kind of signal, in our case the state change of the trackbar. To display both the “Outline” window and the “Source” window,thresh_callback
must be called explicitly once.
C++
const int max_thresh = 255; createTrackbar( "Canny thresh:", source_window, & amp;thresh, max_thresh, thresh_callback ); thresh_callback(0, 0);
Java
sliderPanel.add(new JLabel("Canny threshold: ")); JSlider slider = new JSlider(0, MAX_THRESHOLD, threshold); slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(10); slider.setPaintTicks(true); slider.setPaintLabels(true); slider.addChangeListener(new ChangeListener() {<!-- --> @Override public void stateChanged(ChangeEvent e) {<!-- --> JSlider source = (JSlider) e.getSource(); threshold = source.getValue(); update(); } });
Python
max_thresh = 255 thresh = 100 # initial threshold cv.createTrackbar('Canny thresh:', source_window, thresh, max_thresh, thresh_callback) thresh_callback(thresh)
The callback function does all the fun work.
- Use cv::Canny to detect edges in images.
C++
Mat canny_output; Canny(src_gray, canny_output, thresh, thresh*2);
Java
Mat cannyOutput = new Mat(); Imgproc.Canny(srcGray, cannyOutput, threshold, threshold * 2);
Python
# Detect edges using Canny canny_output = cv.Canny(src_gray, threshold, threshold * 2)
- Find contours and save them into vectors
contour
andhierarchy
.
C++
vector<vector<Point> > contours; findContours( canny_output, contours, RETR_TREE, CHAIN_APPROX_SIMPLE );
Java
List<MatOfPoint> contours = new ArrayList<>(); Mat hierarchy = new Mat(); Imgproc.findContours(cannyOutput, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);
Python
# Find contours contours, _ = cv.findContours(canny_output, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
- For each contour found, we now perform an approximation of the polygon with an accuracy of ±3, stating that the curve must be closed. We then find a bounding rectangle for each polygon and save it into a
boundRect
. Finally, we find a minimum enclosing circle for each polygon and save it ascenter
andradius
vectors.
C++
vector<vector<Point> > contours_poly( contours.size() ); vector<Rect> boundRect( contours.size() ); vector<Point2f>centers( contours.size() ); vector<float>radius( contours.size() ); for( size_t i = 0; i < contours.size(); i + + ) {<!-- --> approxPolyDP( contours[i], contours_poly[i], 3, true ); boundRect[i] = boundingRect( contours_poly[i] ); minEnclosingCircle( contours_poly[i], centers[i], radius[i] ); }
Java
MatOfPoint2f[] contoursPoly = new MatOfPoint2f[contours.size()]; Rect[] boundRect = new Rect[contours.size()]; Point[] centers = new Point[contours.size()]; float[][] radius = new float[contours.size()][1]; for (int i = 0; i < contours.size(); i + + ) {<!-- --> contoursPoly[i] = new MatOfPoint2f(); Imgproc.approxPolyDP(new MatOfPoint2f(contours.get(i).toArray()), contoursPoly[i], 3, true); boundRect[i] = Imgproc.boundingRect(new MatOfPoint(contoursPoly[i].toArray())); centers[i] = new Point(); Imgproc.minEnclosingCircle(contoursPoly[i], centers[i], radius[i]); }
Python
# Approximate contours to polygons + get bounding rects and circles contours_poly = [None]*len(contours) boundRect = [None]*len(contours) centers = [None]*len(contours) radius = [None]*len(contours) for i, c in enumerate(contours): contours_poly[i] = cv.approxPolyDP(c, 3, True) boundRect[i] = cv.boundingRect(contours_poly[i]) centers[i], radius[i] = cv.minEnclosingCircle(contours_poly[i])
We found everything we needed, now all we have to do is draw.
- Creates a new unsigned 8-bit character Mat, padded with zeros. It will contain all the shapes we want to draw (rectangles and circles).
C++
Mat drawing = Mat::zeros( canny_output.size(), CV_8UC3 );
Java
Mat drawing = Mat.zeros(cannyOutput.size(), CvType.CV_8UC3);
Python
drawing = np.zeros((canny_output.shape[0], canny_output.shape[1], 3), dtype=np.uint8)
- For each contour: randomly select a color and use it to draw the contour, bounding rectangle and minimum enclosing circle.
C++
for( size_t i = 0; i< contours.size(); i + + ) {<!-- --> Scalar color = Scalar( rng.uniform(0, 256), rng.uniform(0,256), rng.uniform(0,256) ); drawContours( drawing, contours_poly, (int)i, color ); rectangle( drawing, boundRect[i].tl(), boundRect[i].br(), color, 2 ); circle(drawing, centers[i], (int)radius[i], color, 2); }
Java
List<MatOfPoint> contoursPolyList = new ArrayList<>(contoursPoly.length); for (MatOfPoint2f poly : contoursPoly) {<!-- --> contoursPolyList.add(new MatOfPoint(poly.toArray())); } for (int i = 0; i < contours.size(); i + + ) {<!-- --> Scalar color = new Scalar(rng.nextInt(256), rng.nextInt(256), rng.nextInt(256)); Imgproc.drawContours(drawing, contoursPolyList, i, color); Imgproc.rectangle(drawing, boundRect[i].tl(), boundRect[i].br(), color, 2); Imgproc.circle(drawing, centers[i], (int) radius[i][0], color, 2); }
Python
# Draw polygonal contour + bonding rects + circles for i in range(len(contours)): color = (rng.randint(0,256), rng.randint(0,256), rng.randint(0,256)) cv.drawContours(drawing, contours_poly, i, color) cv.rectangle(drawing, (int(boundRect[i][0]), int(boundRect[i][1])), \ (int(boundRect[i][0] + boundRect[i][2]), int(boundRect[i][1] + boundRect[i][3])), color, 2) cv.circle(drawing, (int(centers[i][0]), int(centers[i][1])), int(radius[i]), color, 2)
- Show results: Create a new window “Contour” and display everything we added to the drawing.
C++
imshow( "Contours", drawing );
Java
imgContoursLabel.setIcon(new ImageIcon(HighGui.toBufferedImage(drawing))); frame.repaint();
Python
# Show in a window cv.imshow('Contours', drawing)
Show results
That’s it: