I'm using CvFindContours to get the contours in an image and then
iterating over all points in the returned contours. CvFindContours is
fast but iterating over the contour points takes a long time. For
example, in an image with about 350 contours consisting of a total of
2500 points it takes 50 seconds to iterate over the points.
Searching the mailing list I found this:
http://code.google.com/p/javacv/issues/detail?id=10.
It mentions that JNA is slow for large structures and suggests setting
autoSynch to false. Surprisingly, doing that made it slower - it took
130 seconds to iterate over the points.
Am I doing this right? Is there a faster way? I'd like process images
in real time.
I've included the test program below. printContoursWithGetStructure()
gets the contours with autoSync defaulting to true. It takes about
400 milliseconds to execute firstContour.getStructure() and then takes
about 50 seconds to iterate over the points.
printContoursWithSyncFalse() gets the contours with autoSync set to
false and uses readField().
Here's the program:
import com.googlecode.javacv.jna.cxcore;
import com.sun.jna.Pointer;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import static com.googlecode.javacv.jna.cxcore.*;
import static com.googlecode.javacv.jna.highgui.*;
import static com.googlecode.javacv.jna.cv.*;
/*
* Example results:
findContours(): numContours: 345
printContoursWithGetStructure(): took 443 millis for
firstContour.getStructure()
printContoursWithGetStructure(): took 52278 millis to iterate over all
points of all contours
printContoursWithGetStructure(): numPoints: 2457, numContours: 345
printContoursWithSyncFalse(): took 0 millis for new
CvContour(firstContour.getValue())
printContoursWithSyncFalse(): h_next is null
printContoursWithSyncFalse(): took 131262 millis to iterate over all
points of all contours
printContoursWithSyncFalse(): numPoints: 2457, numContours: 345
*/
public class TestCvFindContours
{
private static IplImage loadImage(String filename)
{
IplImage image = cvLoadImage(filename, 1);
if (image == null)
{
String msg = "loadImage(): Error calling cvLoadImage on `"
+ filename + "'";
System.err.println(msg);
throw new RuntimeException(msg);
}
return image;
}
private static IplImage cannyImage(IplImage inImage)
{
CvSize.ByValue size = cxcore.cvSize(inImage.width,
inImage.height);
IplImage grayImage = cxcore.cvCreateImage(size,
cxcore.IPL_DEPTH_8U, 1);
IplImage cannyImage = cxcore.cvCreateImage(size,
cxcore.IPL_DEPTH_8U, 1);
cvCvtColor(inImage, grayImage, CV_RGB2GRAY);
cvCanny(grayImage, cannyImage, 10, 100, 3);
return cannyImage;
}
private static CvSeq.PointerByReference findContours(IplImage
cannyImage,
CvMemStorage
storage)
{
CvSeq.PointerByReference firstContour = new
CvSeq.PointerByReference();
int sizeofCvContour =
com.sun.jna.Native.getNativeSize(CvContour.ByValue.class);
int numContours = cvFindContours(cannyImage, storage,
firstContour,
sizeofCvContour,
CV_RETR_EXTERNAL,
CV_CHAIN_APPROX_SIMPLE);
System.out.println("findContours(): numContours: " +
numContours);
return firstContour;
}
private static void
printContoursWithGetStructure(CvSeq.PointerByReference firstContour)
{
long start = System.currentTimeMillis();
CvSeq contour = firstContour.getStructure();
long millis = System.currentTimeMillis() - start;
System.out.println("printContoursWithGetStructure(): took " +
millis + " millis for firstContour.getStructure()");
int numPoints = 0;
int numContours = 0;
start = System.currentTimeMillis();
while (contour != null)
{
++numContours;
// System.out.println("Bounding box of contour: " +
cvBoundingRect(contour, 0));
// Print all points in contour
for (int i = 0; i < contour.total; ++i)
{
++numPoints;
Pointer ptrPoint = cvGetSeqElem(contour, i);
CvPoint point = new CvPoint(ptrPoint);
// System.out.println(point);
}
contour = contour.h_next;
}
millis = System.currentTimeMillis() - start;
System.out.println("printContoursWithGetStructure(): took " +
millis + " millis to iterate over all points of all contours");
System.out.println("printContoursWithGetStructure():
numPoints: " + numPoints + ", numContours: " + numContours);
}
private static void
printContoursWithSyncFalse(CvSeq.PointerByReference firstContour)
{
CvContour.autoSynch = false;
long start = System.currentTimeMillis();
CvContour cvContour = new CvContour(firstContour.getValue());
long millis = System.currentTimeMillis() - start;
System.out.println("printContoursWithSyncFalse(): took " +
millis + " millis for new CvContour(firstContour.getValue())");
int numPoints = 0;
int numContours = 0;
start = System.currentTimeMillis();
while (cvContour != null)
{
++numContours;
cvContour.readField("rect");
cvContour.readField("total");
cvContour.readField("h_next");
// System.out.println("Bounding box of contour: " +
cvBoundingRect(cvContour, 0));
// Print all points in contour
for (int i = 0; i < cvContour.total; ++i)
{
++numPoints;
Pointer ptrPoint = cvGetSeqElem(cvContour, i);
CvPoint point = new CvPoint(ptrPoint);
// System.out.println(point);
}
CvSeq.ByReference hNext = cvContour.h_next;
if (hNext == null)
{
System.out.println("printContoursWithSyncFalse():
h_next is null");
break;
}
cvContour = new CvContour(hNext.getPointer());
}
millis = System.currentTimeMillis() - start;
System.out.println("printContoursWithSyncFalse(): took " +
millis + " millis to iterate over all points of all contours");
System.out.println("printContoursWithSyncFalse(): numPoints: "
+ numPoints + ", numContours: " + numContours);
}
public static void main(String[] args) throws IOException
{
IplImage origImage = loadImage("Pong-splash.png"); // Pong-
mid-game.png
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ImagePanel panel = new ImagePanel();
frame.setContentPane(panel);
IplImage cannyImage = cannyImage(origImage);
CvMemStorage storage = CvMemStorage.create();
CvSeq.PointerByReference firstContour =
findContours(cannyImage, storage);
printContoursWithGetStructure(firstContour);
printContoursWithSyncFalse(firstContour);
cvClearMemStorage(storage);
panel.setImage(cannyImage.getBufferedImage());
cvReleaseImage(origImage.pointerByReference());
cvReleaseImage(cannyImage.pointerByReference());
frame.pack();
frame.setVisible(true);
}
}
class ImagePanel extends JPanel
{
private BufferedImage image;
@Override
public void paintComponent(Graphics g)
{
g.drawImage(image, 0, 0, null);
}
public void setImage(BufferedImage image)
{
this.image = image;
setPreferredSize(new Dimension(image.getWidth(),
image.getHeight()));
}
}