import org.bytedeco.javacpp.indexer.DoubleIndexer;
import org.bytedeco.javacpp.indexer.IntIndexer;
import org.bytedeco.javacpp.opencv_core.*;
import org.bytedeco.javacpp.tools.Slf4jLogger;
import org.bytedeco.javacv.CanvasFrame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.bytedeco.javacv.OpenCVFrameConverter;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import static org.bytedeco.javacpp.opencv_core.*;
import static org.bytedeco.javacpp.opencv_imgproc.*;
/**
* PrincipalComponentAnalysis with JavaCV
* https://github.com/bytedeco/javacv
* Based on "Introduction to Principal Component Analysis (PrincipalComponentAnalysis) ":
* http://docs.opencv.org/3.0.0/d1/dee/tutorial_introduction_to_pca.html
*
* @author Maurice Betzel
*/
public class PrincipalComponentAnalysis {
static {
System.setProperty("org.bytedeco.javacpp.logger", "slf4jlogger");
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug");
}
private static final Slf4jLogger logger = (Slf4jLogger) org.bytedeco.javacpp.tools.Logger.create(PrincipalComponentAnalysis.class);
public static void main(String[] args) {
try {
logger.info(String.valueOf(logger.isDebugEnabled()));
logger.info("Start");
new PrincipalComponentAnalysis().execute(args);
logger.info("Stop");
} catch (Exception e) {
e.printStackTrace();
}
}
private void execute(String[] args) throws Exception {
// If no params provided, compute the default image
BufferedImage bufferedImage = args.length >= 1 ? ImageIO.read(new File(args[0])) : ImageIO.read(this.getClass().getResourceAsStream("shapes2.jpg"));
System.out.println("Image type: " + bufferedImage.getType());
// Convert BufferedImage to Mat and create AutoCloseable objects
try (Mat matrix = new OpenCVFrameConverter.ToMat().convert(new Java2DFrameConverter().convert(bufferedImage));
Mat mask = new Mat();
Mat gray = new Mat();
Mat denoised = new Mat();
Mat bin = new Mat();
Mat hierarchy = new Mat();
MatVector contours = new MatVector()) {
printMat(matrix);
cvtColor(matrix, gray, COLOR_BGR2GRAY);
//Normalize
GaussianBlur(gray, denoised, new Size(5, 5), 0);
threshold(denoised, mask, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
normalize(gray, gray, 0, 255, NORM_MINMAX, -1, mask);
// Convert image to binary
threshold(gray, bin, 150, 255, THRESH_BINARY);
// Find contours
findContours(bin, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE);
long contourCount = contours.size();
System.out.println("Countour count " + contourCount);
for (int i = 0; i < contourCount; ++i) {
// Calculate the area of each contour
Mat contour = contours.get(i);
double area = contourArea(contour);
// Ignore contours that are too small or too large
if (area > 128 && area < 8192) {
principalComponentAnalysis(contour, i, matrix);
}
}
CanvasFrame canvas = new CanvasFrame("PrincipalComponentAnalysis", 1);
canvas.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
canvas.setCanvasSize(320, 240);
OpenCVFrameConverter converter = new OpenCVFrameConverter.ToIplImage();
canvas.showImage(converter.convert(matrix));
}
}
// contour is a one dimensional array
private void principalComponentAnalysis(Mat contour, int entry, Mat matrix) throws Exception {
PCA pca_analysis = null;
Mat mean = null;
Mat eigenVector = null;
Mat eigenValues = null;
//Construct a buffer used by the pca analysis
try (Mat data_pts = new Mat(contour.rows(), 2, CV_64FC1);
Mat placeholder = new Mat();
Point cntr = new Point()) {
IntIndexer contourIndexer = contour.createIndexer();
DoubleIndexer data_idx = data_pts.createIndexer();
for (int i = 0; i < contour.rows(); i++) {
data_idx.put(i, 0, contourIndexer.get(i, 0));
data_idx.put(i, 1, contourIndexer.get(i, 1));
}
contourIndexer.release();
data_idx.release();
//Perform PrincipalComponentAnalysis analysis
ArrayList<Point2d> eigen_vecs = new ArrayList(2);
ArrayList<Double> eigen_val = new ArrayList(2);
pca_analysis = new PCA(data_pts, placeholder, CV_PCA_DATA_AS_ROW);
mean = pca_analysis.mean();
eigenVector = pca_analysis.eigenvectors();
eigenValues = pca_analysis.eigenvalues();
DoubleIndexer mean_idx = mean.createIndexer();
DoubleIndexer eigenVectorIndexer = eigenVector.createIndexer();
DoubleIndexer eigenValuesIndexer = eigenValues.createIndexer();
for (int i = 0; i < 2; ++i) {
eigen_vecs.add(new Point2d(eigenVectorIndexer.get(i, 0), eigenVectorIndexer.get(i, 1)));
eigen_val.add(eigenValuesIndexer.get(0, i));
}
double cntrX = mean_idx.get(0, 0);
double cntrY = mean_idx.get(0, 1);
mean_idx.release();
eigenVectorIndexer.release();
eigenValuesIndexer.release();
double x1 = cntrX + 0.02 * (eigen_vecs.get(0).x() * eigen_val.get(0));
double y1 = cntrY + 0.02 * (eigen_vecs.get(0).y() * eigen_val.get(0));
double x2 = cntrX - 0.02 * (eigen_vecs.get(1).x() * eigen_val.get(1));
double y2 = cntrY - 0.02 * (eigen_vecs.get(1).y() * eigen_val.get(1));
// Draw the principal components, keep accuracy during calculations
cntr.x((int) Math.rint(cntrX));
cntr.y((int) Math.rint(cntrY));
circle(matrix, cntr, 5, new Scalar(255, 0, 255, 0));
double radian1 = Math.atan2(cntrY - y1, cntrX - x1);
double radian2 = Math.atan2(cntrY - y2, cntrX - x2);
double hypotenuse1 = Math.sqrt((cntrY - y1) * (cntrY - y1) + (cntrX - x1) * (cntrX - x1));
double hypotenuse2 = Math.sqrt((cntrY - y2) * (cntrY - y2) + (cntrX - x2) * (cntrX - x2));
//Enhance the vector signal by a factor of 2
double point1x = cntrX - 2 * hypotenuse1 * Math.cos(radian1);
double point1y = cntrY - 2 * hypotenuse1 * Math.sin(radian1);
double point2x = cntrX - 2 * hypotenuse2 * Math.cos(radian2);
double point2y = cntrY - 2 * hypotenuse2 * Math.sin(radian2);
drawAxis(matrix, radian1, cntr, point1x, point1y, Scalar.BLUE);
drawAxis(matrix, radian2, cntr, point2x, point2y, Scalar.CYAN);
} finally {
if(pca_analysis != null) {
pca_analysis.deallocate();
}
if(mean != null) {
mean.deallocate();
}
if(eigenVector != null) {
eigenVector.deallocate();
}
if(eigenValues != null) {
eigenValues.deallocate();
}
}
}
private void drawAxis(Mat matrix, double radian, Point cntr, double x, double y, Scalar colour) throws Exception {
try(Point q = new Point((int) x, (int) y);
Point arrowHook1 = new Point((int) (q.x() + 9 * Math.cos(radian + CV_PI / 4)), (int) (q.y() + 9 * Math.sin(radian + CV_PI / 4)));
Point arrowHook2 = new Point((int) (q.x() + 9 * Math.cos(radian - CV_PI / 4)), (int) (q.y() + 9 * Math.sin(radian - CV_PI / 4)))) {
// draw
line(matrix, cntr, q, colour);
line(matrix, arrowHook1, q, colour);
line(matrix, arrowHook2, q, colour);
}
}
public static void printMat(Mat mat) {
System.out.println("Channels: " + mat.channels());
System.out.println("Rows: " + mat.rows());
System.out.println("Cols: " + mat.cols());
System.out.println("Type: " + mat.type());
System.out.println("Dims: " + mat.dims());
System.out.println("Depth: " + mat.depth());
}
}