Java tutorial
package hu.unideb.fksz; /* * #%L * Traffic-counter * %% * Copyright (C) 2016 FKSZSoft * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ import java.io.ByteArrayInputStream; import java.util.ArrayList; import java.util.List; import javafx.scene.image.Image; import org.opencv.core.Core; import org.opencv.core.Mat; import org.opencv.core.MatOfByte; import org.opencv.core.MatOfInt; import org.opencv.core.MatOfPoint; import org.opencv.core.Point; import org.opencv.core.Rect; import org.opencv.core.Scalar; import org.opencv.core.Size; import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgproc.Imgproc; import org.opencv.videoio.VideoCapture; import static hu.unideb.fksz.TrafficCounterLogger.logger; /** * Class for processing video files. * The main purpose of this class is to open video files, and read two * frames in a loop, then process the frames, and search for differences, * then for contours. * @author krajsz * */ public class VideoProcessor { static { System.loadLibrary(Core.NATIVE_LIBRARY_NAME); } private VideoCapture video = new VideoCapture(); private Mat frame = new Mat(); private Image fxImage = null; private Mat firstGrayImage = new Mat(); private Mat secondGrayImage = new Mat(); private Mat secondFrame = new Mat(); private Mat differenceOfImages = new Mat(); private Mat thresholdImage = new Mat(); private Mat hierarchy = new Mat(); private Rect boundingRectangle = new Rect(); private String fileName; private List<Point> controlPoints = new ArrayList<Point>(); private int controlPointsHeight = 300; private int previousControlPointsHeight; private boolean wasAtCenterPoint = false; private boolean wasAtLeftPoint = false; private boolean wasAtRightPoint = false; private boolean finished = false; private int carsPerMinute; private int minutes; private int seconds; private int hours; private final Point textPosition = new Point(10, 15); private final Scalar fontColor = new Scalar(0, 50, 255); private MatOfByte buffer = new MatOfByte(); private final MatOfInt params = new MatOfInt(Imgcodecs.IMWRITE_JPEG_QUALITY, 20); private final Size frameSize = new Size(640, 480); private List<MatOfPoint> contours = new ArrayList<MatOfPoint>(); private List<MatOfInt> hullPoints = new ArrayList<MatOfInt>(); private int frameCounter = 0; private int detectedCarsCount = 0; private final Rect imageArea = new Rect(15, 15, 640, 480); /** * Constructor for creating a {@code VideoProcessor} object, * Initializes the {@code controlPoints}. */ public VideoProcessor() { initControlPoints(); } public boolean isOpened() { return video.isOpened(); } /** * Returns an {@code int} , the {@code detectedCarsCount}. * * @return the {@code detectedCarsCount} */ public int getDetectedCarsCount() { return detectedCarsCount; } /** * Returns a {@code double}, the frame count of the current video. * * @return the frame count of the current video. */ public double getFrameCount() { return (getVideoCap().isOpened() ? getVideoCap().get(7) : 0); } /** * Returns an {@code int}, the position of the current frame. * * @return the position of the current frame. */ public int getFramePos() { return (getVideoCap().isOpened() ? (int) getVideoCap().get(1) : 0); } /** * Returns a {@code Mat}, the {@code frame}. * * @return the {@code frame} */ public Mat getFrame() { return this.frame; } /** * Returns a {@code double}, the frame per second value of the {@code video}. * * @return the frame per second value of the {@code video} */ public double getFPS() { return (getVideoCap().isOpened() ? getVideoCap().get(5) : 1); } /** * Returns a {@code VideoCapture}, an OpenCV class for handling videos. * * @return {@code video}, an OpenCV class for handling videos */ public VideoCapture getVideoCap() { return this.video; } /** * Sets the frame position of the {@code VideoCapture} if the value * is valid. * * @param pos the position to be set. */ public void setFramePos(double pos) { if (pos >= 0 && pos <= getFrameCount()) { try { getVideoCap().set(1, (int) pos); } catch (Exception e) { TrafficCounterLogger.errorMessage(e.getMessage()); } } else { logger.error("Invalid positioning number!"); } } /** * Writes the specified {@code text} on the {@code frame}. * @param text the text to be written on the {@code frame}. */ public void writeOnFrame(String text) { Imgproc.putText(getFrame(), text, textPosition, Core.FONT_HERSHEY_SIMPLEX, 0.7, fontColor, 2); } /** * Calculates the length of the loaded video, and returns it as a {@code String}. * * @return the {@code String} representing the length of the video. */ public String getLengthFormatted() { seconds = (int) (getFrameCount() / getFPS()); minutes = (int) (seconds / 60); hours = (int) (minutes / 60); minutes %= 60; seconds %= 60; return (hours > 9 ? hours : "0" + hours) + ":" + (minutes > 9 ? minutes : "0" + minutes) + ":" + (seconds > 9 ? seconds : "0" + seconds); } /** * Processes {@code firstFrame} and {@code secondFrame}. * @param firstFrame the first frame of a cycle. */ private void processFrame(Mat firstFrame) { double contourArea = 0; int position = 0; try { /** * Resizes the {@code firstFrame} to {@code frameSize}. * */ Imgproc.resize(firstFrame, firstFrame, frameSize); /** * Convert the frame in grayscale color space. */ Imgproc.cvtColor(firstFrame, firstGrayImage, Imgproc.COLOR_BGR2GRAY); /** * {@code video} reads the second frame. */ video.read(secondFrame); Imgproc.resize(secondFrame, secondFrame, frameSize); Imgproc.cvtColor(secondFrame, secondGrayImage, Imgproc.COLOR_BGR2GRAY); /** * Getting the absolute per-pixel difference of the two frames into {@code differenceOfImages}. */ Core.absdiff(firstGrayImage, secondGrayImage, differenceOfImages); Imgproc.threshold(differenceOfImages, thresholdImage, 25, 255, Imgproc.THRESH_BINARY); Imgproc.blur(thresholdImage, thresholdImage, new Size(12, 12)); Imgproc.threshold(thresholdImage, thresholdImage, 20, 255, Imgproc.THRESH_BINARY); ///// for (int i = 0; i < contours.size(); ++i) { contours.get(i).release(); } contours.clear(); /** * The horizontal line. */ Imgproc.line(firstFrame, controlPoints.get(6), controlPoints.get(7), new Scalar(255, 0, 0), Imgproc.LINE_4); Imgproc.findContours(thresholdImage, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE); for (int i = 0; i < hullPoints.size(); ++i) { hullPoints.get(i).release(); } hullPoints.clear(); for (int i = 0; i < contours.size(); i++) { MatOfInt tmp = new MatOfInt(); Imgproc.convexHull(contours.get(i), tmp, false); hullPoints.add(tmp); } /** * Searches for the contour with the greatest area. */ if (contours.size() > 0) { for (int i = 0; i < contours.size(); i++) { if (Imgproc.contourArea(contours.get(i)) > contourArea) { contourArea = Imgproc.contourArea(contours.get(i)); position = i; boundingRectangle = Imgproc.boundingRect(contours.get(i)); } } } secondFrame.release(); hierarchy.release(); secondGrayImage.release(); firstGrayImage.release(); thresholdImage.release(); differenceOfImages.release(); } catch (Exception e) { logger.error(e.getMessage()); } /** * Checking whether the control point on the left is * inside of {@code boundingRectangle}, which is a {@code Rect}, * bounding the greatest contour. */ if (controlPoints.get(6).inside(boundingRectangle)) { Imgproc.line(frame, controlPoints.get(0), controlPoints.get(1), new Scalar(0, 0, 255), 2); wasAtLeftPoint = true; } else if (!controlPoints.get(6).inside(boundingRectangle)) { Imgproc.line(frame, controlPoints.get(0), controlPoints.get(1), new Scalar(0, 255, 0), 2); } /** * Checking whether the control point on the middle is * inside of {@code boundingRectangle}, which is a {@code Rect}, * bounding the greatest contour. */ if (controlPoints.get(8).inside(boundingRectangle)) { Imgproc.line(frame, controlPoints.get(2), controlPoints.get(3), new Scalar(0, 0, 255), 2); wasAtCenterPoint = true; } else if (!controlPoints.get(8).inside(boundingRectangle)) { Imgproc.line(frame, controlPoints.get(2), controlPoints.get(3), new Scalar(0, 255, 0), 2); } /** * Checking whether the control point on the right is * inside of {@code boundingRectangle}, which is a {@code Rect}, * bounding the greatest contour. */ if (controlPoints.get(7).inside(boundingRectangle)) { Imgproc.line(frame, controlPoints.get(4), controlPoints.get(5), new Scalar(0, 0, 255), 2); wasAtRightPoint = true; } else if (!controlPoints.get(7).inside(boundingRectangle)) { Imgproc.line(frame, controlPoints.get(4), controlPoints.get(5), new Scalar(0, 255, 0), 2); } /** * If the three control points have were inside the {@code boundingRectangle}, * it means that a "car" has passed. */ if (wasAtCenterPoint && wasAtLeftPoint && wasAtRightPoint) { detectedCarsCount++; wasAtCenterPoint = false; wasAtLeftPoint = false; wasAtRightPoint = false; logger.info("Detected " + detectedCarsCount + " car(s)"); } /** * If the contour is big enough, draw it. */ if (contourArea > 3000) { Imgproc.drawContours(frame, contours, position, new Scalar(255, 255, 255)); } } /** * Does the main loop, if we reach the penultimate frame, * it means we have reached the end of the end of the video. */ public void processVideo() { do { Mat tmp = new Mat(); video.read(tmp); if (!tmp.empty()) { frame = tmp.clone(); tmp.release(); if (frameCounter < (getFrameCount() / 2) - 1) { frameCounter++; if (getMinutes() > 0) { carsPerMinute = getDetectedCarsCount() / getMinutes(); } processFrame(getFrame()); } else { frameCounter = 0; finished = true; logger.trace("Restarting.."); setFramePos(1); } } else { logger.warn("Empty image!"); frameCounter = 0; finished = true; logger.trace("Restarting.."); setFramePos(1); } } while (frameCounter > (getFrameCount() / 2) - 2); } /** * Returns an {@code Image}, converted from a {@code Mat}. * * @param frameToConvert The frame to be converted to a {@code Image} * @return The {@code Image}, converted from a {@code Mat} */ public Image convertCvMatToImage(Mat frameToConvert) { if (!buffer.empty()) { buffer.release(); } try { Imgproc.resize(frameToConvert, frameToConvert, frameSize); Imgcodecs.imencode(".jpg", frameToConvert, buffer, params); } catch (Exception e) { logger.error(e.getMessage()); } fxImage = new Image(new ByteArrayInputStream(buffer.toArray())); if (!frameToConvert.empty()) { frameToConvert.release(); ///// } return fxImage; } /** * Returns an {@code Image}, converted from a {@code Mat}, {@code frame}. * * @return an {@code Image}, converted from a {@code Mat}, {@code frame}. */ public Image convertCvMatToImage() { return convertCvMatToImage(getFrame()); } /** * Gets an {@code Image}, converted from the specified index * of the {@code video}. * * @param pos the position from which the frame is retrieved. * @return an {@code Image}, converted from the specified index * of the {@code video}. */ public Image getImageAtPos(int pos) { if (video.isOpened()) { if (pos < getFrameCount() && pos > 0) { setFramePos(pos); Mat tmp = new Mat(); video.retrieve(tmp); setFramePos(0); try { Imgproc.resize(tmp, tmp, frameSize); } catch (Exception e) { TrafficCounterLogger.errorMessage(e.getMessage()); } return convertCvMatToImage(tmp); } else { return getImageAtPos(1); } } else { logger.error("VideoCapture not opened!"); return null; } } /** * Resets the value of the control points to {@code false}. */ private void resetCheckPoints() { wasAtCenterPoint = false; wasAtLeftPoint = false; wasAtRightPoint = false; } /** * Initializes the {@code controlPoints}, * {@link VideoProcessor#VideoProcessor() in the constructor}. */ private void initControlPoints() { try { controlPoints.add(new Point(80, 100)); controlPoints.add(new Point(80, frameSize.height - 100)); controlPoints.add(new Point(frameSize.width / 2, 100)); controlPoints.add(new Point(frameSize.width / 2, frameSize.height - 100)); controlPoints.add(new Point(frameSize.width - 80, 100)); controlPoints.add(new Point(frameSize.width - 80, frameSize.height - 100)); controlPoints.add(new Point(80, controlPointsHeight)); controlPoints.add(new Point(frameSize.width - 80, controlPointsHeight)); controlPoints.add(new Point(frameSize.width / 2, controlPointsHeight)); TrafficCounterLogger.traceMessage("Control points initialised successfully!"); } catch (Exception e) { logger.error(e.getMessage()); } } /** * Returns an {@code int}, which represents the failure, or the success * of the opening of the video specified by the {@code filename}. * * @param filename The absolute path of the video file to be opened. * @return an {@code int}, which represents the failure, or the success * of the opening of the video specified by the {@code filename} */ public int initVideo(String filename) { if (filename != null) { try { video.open(filename); } catch (Exception e) { e.printStackTrace(); } if (video.isOpened()) { resetCheckPoints(); fileName = filename; finished = false; frameCounter = 0; logger.trace("VideoCapture opened successfully!"); return 0; } logger.error("VideoCapture not opened!"); return 1; } else { logger.warn("File name is null!"); return 1; } } /** * Sets detectedCarsCount. * * @param detectedCarsCount to be set. */ public void setDetectedCarsCount(int detectedCarsCount) { this.detectedCarsCount = detectedCarsCount; } /** * Returns an {@code int}, {@code minutes}, the length of the loaded video in minutes. * * @return the length of the loaded video in minutes. */ public int getMinutes() { return minutes; } /** * Returns a {@code boolean}, whether the video has finished or not. * * @return whether the video has finished or not. */ public boolean isFinished() { return finished; } /** * Sets {@code finished}. * * @param finished to be set. */ public void setFinished(boolean finished) { this.finished = finished; } /** * Returns an {@code int}, the height of the {@code controlPoints}. * * @return the height of the {@code controlPoints}. */ public int getControlPointsHeight() { return controlPointsHeight; } /** * Returns an {@code int}, the cars per minute value of the video. * * @return the cars per minute value of the video. */ public int getCarsPerMinute() { return carsPerMinute; } /** * Sets {@code carsPerMinute}. * * @param carsPerMinute to be set. */ public void setCarsPerMinute(int carsPerMinute) { this.carsPerMinute = carsPerMinute; } /** * Returns {@code previousControlPointsHeight}, the previous height of the {@code controlPoints}. * * @return the previous height of the {@code controlPoints}. */ public int getPreviousControlPointsHeight() { return previousControlPointsHeight; } /** * Sets {@code previousControlPointsHeight}. * * @param previousControlPointsHeight to be set. */ public void setPreviousControlPointsHeight(int previousControlPointsHeight) { this.previousControlPointsHeight = previousControlPointsHeight; } /** * Returns a {@code Rect}, the designated image area represented by a rectangle. * * @return the designated image area represented by a rectangle. */ public Rect getImageArea() { return imageArea; } /** * Returns the height of a control point. * * @return the height of a control point. */ public double getHeightOfAControlPoint() { return controlPoints.get(6).y; } /** * Sets height of the three control points. * * @param height to be set. */ public void setHeightOfTheControlPoints(double height) { controlPoints.get(6).y = height; controlPoints.get(7).y = height; controlPoints.get(8).y = height; } /** * Returns the file name of the opened video. * * @return {@code filename}, the file name of the opened video. */ public String getFileName() { return fileName; } }