2D feature framework – detecting corner positions in sub-pixels OpenCV v4.8.0

Previous tutorial: Create your own corner detector

Next tutorial: Feature Detection

Original author Ana Huamán
Compatibility OpenCV >= 3.0

Goals

In this tutorial you will learn how to

  • Use the OpenCV function cv::cornerSubPix to find a more precise corner position (more precise than integer pixels).

Code

C++
The tutorial code is shown below. You can also download it from here

#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
Mat src, src_gray;
int maxCorners = 10;
int maxTrackbar = 25;
RNG rng(12345);
const char* source_window = "Image";
void goodFeaturesToTrack_Demo( int, void* );
int main(int argc, char** argv)
{<!-- -->
 CommandLineParser parser( argc, argv, "{@input | pic3.png | input image}" );
 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 );
 namedWindow( source_window );
 createTrackbar( "Max corners:", source_window, & amp;maxCorners, maxTrackbar, goodFeaturesToTrack_Demo );
 imshow( source_window, src );
 goodFeaturesToTrack_Demo(0, 0);
 waitKey();
 return 0;
}
void goodFeaturesToTrack_Demo( int, void* )
{<!-- -->
 maxCorners = MAX(maxCorners, 1);
 vector<Point2f> corners;
 double qualityLevel = 0.01;
 double minDistance = 10;
 int blockSize = 3, gradientSize = 3;
 bool useHarrisDetector = false;
 double k = 0.04;
 Mat copy = src.clone();
 goodFeaturesToTrack( src_gray,
 corners,
 maxCorners,
 qualityLevel,
 minDistance,
 Mat(),
 blockSize,
 gradientSize,
 useHarrisDetector,
 k );
 cout << "** Number of corners detected: " << corners.size() << endl;
 int radius = 4;
 for( size_t i = 0; i < corners.size(); i + + )
 {<!-- -->
 circle( copy, corners[i], radius, Scalar(rng.uniform(0,255), rng.uniform(0, 256), rng.uniform(0, 256)), FILLED );
 }
 namedWindow( source_window );
 imshow( source_window, copy );
 Size winSize = Size( 5, 5 );
 Size zeroZone = Size( -1, -1 );
 TermCriteria criteria = TermCriteria( TermCriteria::EPS + TermCriteria::COUNT, 40, 0.001 );
 cornerSubPix( src_gray, corners, winSize, zeroZone, criteria );
 for( size_t i = 0; i < corners.size(); i + + )
 {<!-- -->
 cout << " -- Refined Corner [" << i << "] (" << corners[i].x << "," << corners[i].y << ")" << endl;
 }
}

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.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.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.core.TermCriteria;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
class CornerSubPix {<!-- -->
 private Mat src = new Mat();
 private Mat srcGray = new Mat();
 private JFrame frame;
 private JLabel imgLabel;
 private static final int MAX_CORNERS = 25;
 private int maxCorners = 10;
 private Random rng = new Random(12345);
 public CornerSubPix(String[] args) {<!-- -->
 String filename = args.length > 0 ? args[0] : "../data/pic3.png";
 src = Imgcodecs.imread(filename);
 if (src.empty()) {<!-- -->
 System.err.println("Cannot read image: " + filename);
 System.exit(0);
 }
 Imgproc.cvtColor(src, srcGray, Imgproc.COLOR_BGR2GRAY);
// Create and set up the window.
frame = new JFrame("Shi-Tomasi corner detector 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("Max corners:"));
 JSlider slider = new JSlider(0, MAX_CORNERS, maxCorners);
 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();
 maxCorners = source.getValue();
 update();
 }
 });
 sliderPanel.add(slider);
 pane.add(sliderPanel, BorderLayout.PAGE_START);
 imgLabel = new JLabel(new ImageIcon(img));
 pane.add(imgLabel, BorderLayout.CENTER);
 }
 private void update() {<!-- -->
 maxCorners = Math.max(maxCorners, 1);
 MatOfPoint corners = new MatOfPoint();
 double qualityLevel = 0.01;
 double minDistance = 10;
 int blockSize = 3, gradientSize = 3;
 boolean useHarrisDetector = false;
 double k = 0.04;
 Mat copy = src.clone();
 Imgproc.goodFeaturesToTrack(srcGray, corners, maxCorners, qualityLevel, minDistance, new Mat(),
 blockSize, gradientSize, useHarrisDetector, k);
 System.out.println("** Number of corners detected: " + corners.rows());
 int[] cornersData = new int[(int) (corners.total() * corners.channels())];
 corners.get(0, 0, cornersData);
 int radius = 4;
 Mat matCorners = new Mat(corners.rows(), 2, CvType.CV_32F);
 float[] matCornersData = new float[(int) (matCorners.total() * matCorners.channels())];
 matCorners.get(0, 0, matCornersData);
 for (int i = 0; i < corners.rows(); i + + ) {<!-- -->
 Imgproc.circle(copy, new Point(cornersData[i * 2], cornersData[i * 2 + 1]), radius,
 new Scalar(rng.nextInt(256), rng.nextInt(256), rng.nextInt(256)), Imgproc.FILLED);
 matCornersData[i * 2] = cornersData[i * 2];
 matCornersData[i * 2 + 1] = cornersData[i * 2 + 1];
 }
 matCorners.put(0, 0, matCornersData);
 imgLabel.setIcon(new ImageIcon(HighGui.toBufferedImage(copy)));
 frame.repaint();
 Size winSize = new Size(5, 5);
 Size zeroZone = new Size(-1, -1);
 TermCriteria criteria = new TermCriteria(TermCriteria.EPS + TermCriteria.COUNT, 40, 0.001);
 Imgproc.cornerSubPix(srcGray, matCorners, winSize, zeroZone, criteria);
 matCorners.get(0, 0, matCornersData);
 for (int i = 0; i < corners.rows(); i + + ) {<!-- -->
 System.out.println(
 " -- Refined Corner [" + i + "] (" + matCornersData[i * 2] + "," + matCornersData[i * 2 + 1] + ")");
 }
 }
}
public class CornerSubPixDemo {<!-- -->
 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 CornerSubPix(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
source_window = 'Image'
maxTrackbar = 25
rng.seed(12345)
def goodFeaturesToTrack_Demo(val):
 maxCorners = max(val, 1)
 # Parameters of Shi-Tomasi algorithm
 qualityLevel = 0.01
 minDistance=10
 blockSize = 3
 gradientSize = 3
 useHarrisDetector = False
 k = 0.04
 #Copy source image
 copy = np.copy(src)
 # Apply corner detection
 corners = cv.goodFeaturesToTrack(src_gray, maxCorners, qualityLevel, minDistance, None, \
 blockSize=blockSize, gradientSize=gradientSize, useHarrisDetector=useHarrisDetector, k=k)
 # Detected drawing corners
 print('** Number of corners detected:', corners.shape[0])
 radius = 4
 for i in range(corners.shape[0]):
 cv.circle(copy, (int(corners[i,0,0]), int(corners[i,0,1])), radius, (rng.randint(0,256), rng.randint(0,256), rng .randint(0,256)), cv.FILLED)
 # Display the results obtained
 cv.namedWindow(source_window)
 cv.imshow(source_window, copy)
 # Set the required parameters to find delicate corners
 winSize = (5, 5)
 zeroZone = (-1, -1)
 criteria = (cv.TERM_CRITERIA_EPS + cv.TermCriteria_COUNT, 40, 0.001)
 # Calculate the angular position of the refinement
 corners = cv.cornerSubPix(src_gray, corners, winSize, zeroZone, criteria)
 # write down
 for i in range(corners.shape[0]):
 print(" -- Refined Corner [", i, "] (", corners[i,0,0], ",", corners[i,0,1], ")")
# Load source image and convert to grayscale image
parser = argparse.ArgumentParser(description='Code for Shi-Tomasi corner detector tutorial.')
parser.add_argument('--input', help='Path to input image.', default='pic3.png')
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)
src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
#Create windows and track bars
cv.namedWindow(source_window)
maxCorners = 10 #Initial threshold
cv.createTrackbar('Threshold: ', source_window, maxCorners, maxTrackbar, goodFeaturesToTrack_Demo)
cv.imshow(source_window, src)
goodFeaturesToTrack_Demo(maxCorners)
cv.waitKey()

Results

The results are as follows