我正在尝试从下面的代码中获取所有OpenCV类,并将其放入自己的类中,以便可以调用这些方法而无需使用ObjRecognitionController类。另外,我正在尝试使ObjRecognitionController类成为Singleton,以便确保只有一个类可以访问网络摄像头。
想要这样做的原因有两个:
我想遵守最佳实践代码约定。现在,ObjRecognitionController类(我已经对其进行了很大的扩展)超过了1000行代码,并且我认为与使用OpenCV进行图像分析有关的任何事情都应该在其自己的类中。
我需要能够从外部设备调用OpenCV方法,并担心这样做会创建额外的线程并浪费资源。
以下所有代码均摘自https://github.com/opencv-java/object-detection
我试图通过实现接口将所有OpenCV方法取出并带入它们自己的类中,但是由于要从控制器类等访问某些本机方法,所以出现了很多错误。网络摄像头和GUI滑块,使用fxml文件实现。
我也试图将控制器设置为Singleton,但出现了很多错误。请参阅下面的错误消息。
任何帮助将不胜感激。谢谢
Error
Caused by: java.lang.IllegalAccessException: Class sun.reflect.misc.ReflectUtil can not access a member of class application.FXController with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Unknown Source)
at java.lang.Class.newInstance(Unknown Source)
at sun.reflect.misc.ReflectUtil.newInstance(Unknown Source)
at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:927)
ObjRecognition.java
package it.polito.teaching.cv;
import org.opencv.core.Core;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.fxml.FXMLLoader;
public class ObjRecognition extends Application
{
/**
* The main class for a JavaFX application. It creates and handles the main
* window with its resources (style, graphics, etc.).
*
* This application looks for any tennis ball in the camera video stream and
* try to select them according to their HSV values. Found tennis balls are
* framed with a blue line.
*
* @author <a href="mailto:luigi.derussis@polito.it">Luigi De Russis</a>
* @version 2.0 (2017-03-10)
* @since 1.0 (2015-01-13)
*
*/
@Override
public void start(Stage primaryStage)
{
try
{
// load the FXML resource
FXMLLoader loader = new FXMLLoader(getClass().getResource("ObjRecognition.fxml"));
// store the root element so that the controllers can use it
BorderPane root = (BorderPane) loader.load();
// set a whitesmoke background
root.setStyle("-fx-background-color: whitesmoke;");
// create and style a scene
Scene scene = new Scene(root, 800, 600);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
// create the stage with the given title and the previously created
// scene
primaryStage.setTitle("Object Recognition");
primaryStage.setScene(scene);
// show the GUI
primaryStage.show();
// set the proper behavior on closing the application
ObjRecognitionController controller = loader.getController();
primaryStage.setOnCloseRequest((new EventHandler<WindowEvent>() {
public void handle(WindowEvent we)
{
controller.setClosed();
}
}));
}
catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
// load the native OpenCV library
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
launch(args);
}
}
ObjRecognitionController.java
package it.polito.teaching.cv;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import org.opencv.videoio.VideoCapture;
import it.polito.elite.teaching.cv.utils.Utils;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
/**
* The controller associated with the only view of our application. The
* application logic is implemented here. It handles the button for
* starting/stopping the camera, the acquired video stream, the relative
* controls and the image segmentation process.
*
* @author <a href="mailto:luigi.derussis@polito.it">Luigi De Russis</a>
* @version 2.0 (2017-03-10)
* @since 1.0 (2015-01-13)
*
*/
public class ObjRecognitionController
{
// FXML camera button
@FXML
private Button cameraButton;
// the FXML area for showing the current frame
@FXML
private ImageView originalFrame;
// the FXML area for showing the mask
@FXML
private ImageView maskImage;
// the FXML area for showing the output of the morphological operations
@FXML
private ImageView morphImage;
// FXML slider for setting HSV ranges
@FXML
private Slider hueStart;
@FXML
private Slider hueStop;
@FXML
private Slider saturationStart;
@FXML
private Slider saturationStop;
@FXML
private Slider valueStart;
@FXML
private Slider valueStop;
// FXML label to show the current values set with the sliders
@FXML
private Label hsvCurrentValues;
// a timer for acquiring the video stream
private ScheduledExecutorService timer;
// the OpenCV object that performs the video capture
private VideoCapture capture = new VideoCapture();
// a flag to change the button behavior
private boolean cameraActive;
// property for object binding
private ObjectProperty<String> hsvValuesProp;
/**
* The action triggered by pushing the button on the GUI
*/
@FXML
private void startCamera()
{
// bind a text property with the string containing the current range of
// HSV values for object detection
hsvValuesProp = new SimpleObjectProperty<>();
this.hsvCurrentValues.textProperty().bind(hsvValuesProp);
// set a fixed width for all the image to show and preserve image ratio
this.imageViewProperties(this.originalFrame, 400);
this.imageViewProperties(this.maskImage, 200);
this.imageViewProperties(this.morphImage, 200);
if (!this.cameraActive)
{
// start the video capture
this.capture.open(0);
// is the video stream available?
if (this.capture.isOpened())
{
this.cameraActive = true;
// grab a frame every 33 ms (30 frames/sec)
Runnable frameGrabber = new Runnable() {
@Override
public void run()
{
// effectively grab and process a single frame
Mat frame = grabFrame();
// convert and show the frame
Image imageToShow = Utils.mat2Image(frame);
updateImageView(originalFrame, imageToShow);
}
};
this.timer = Executors.newSingleThreadScheduledExecutor();
this.timer.scheduleAtFixedRate(frameGrabber, 0, 33, TimeUnit.MILLISECONDS);
// update the button content
this.cameraButton.setText("Stop Camera");
}
else
{
// log the error
System.err.println("Failed to open the camera connection...");
}
}
else
{
// the camera is not active at this point
this.cameraActive = false;
// update again the button content
this.cameraButton.setText("Start Camera");
// stop the timer
this.stopAcquisition();
}
}
/**
* Get a frame from the opened video stream (if any)
*
* @return the {@link Image} to show
*/
private Mat grabFrame()
{
Mat frame = new Mat();
// check if the capture is open
if (this.capture.isOpened())
{
try
{
// read the current frame
this.capture.read(frame);
// if the frame is not empty, process it
if (!frame.empty())
{
// init
Mat blurredImage = new Mat();
Mat hsvImage = new Mat();
Mat mask = new Mat();
Mat morphOutput = new Mat();
// remove some noise
Imgproc.blur(frame, blurredImage, new Size(7, 7));
// convert the frame to HSV
Imgproc.cvtColor(blurredImage, hsvImage, Imgproc.COLOR_BGR2HSV);
// get thresholding values from the UI
// remember: H ranges 0-180, S and V range 0-255
Scalar minValues = new Scalar(this.hueStart.getValue(), this.saturationStart.getValue(),
this.valueStart.getValue());
Scalar maxValues = new Scalar(this.hueStop.getValue(), this.saturationStop.getValue(),
this.valueStop.getValue());
// show the current selected HSV range
String valuesToPrint = "Hue range: " + minValues.val[0] + "-" + maxValues.val[0]
+ "\tSaturation range: " + minValues.val[1] + "-" + maxValues.val[1] + "\tValue range: "
+ minValues.val[2] + "-" + maxValues.val[2];
Utils.onFXThread(this.hsvValuesProp, valuesToPrint);
// threshold HSV image to select tennis balls
Core.inRange(hsvImage, minValues, maxValues, mask);
// show the partial output
this.updateImageView(this.maskImage, Utils.mat2Image(mask));
// morphological operators
// dilate with large element, erode with small ones
Mat dilateElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(24, 24));
Mat erodeElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(12, 12));
Imgproc.erode(mask, morphOutput, erodeElement);
Imgproc.erode(morphOutput, morphOutput, erodeElement);
Imgproc.dilate(morphOutput, morphOutput, dilateElement);
Imgproc.dilate(morphOutput, morphOutput, dilateElement);
// show the partial output
this.updateImageView(this.morphImage, Utils.mat2Image(morphOutput));
// find the tennis ball(s) contours and show them
frame = this.findAndDrawBalls(morphOutput, frame);
}
}
catch (Exception e)
{
// log the (full) error
System.err.print("Exception during the image elaboration...");
e.printStackTrace();
}
}
return frame;
}
/**
* Given a binary image containing one or more closed surfaces, use it as a
* mask to find and highlight the objects contours
*
* @param maskedImage
* the binary image to be used as a mask
* @param frame
* the original {@link Mat} image to be used for drawing the
* objects contours
* @return the {@link Mat} image with the objects contours framed
*/
private Mat findAndDrawBalls(Mat maskedImage, Mat frame)
{
// init
List<MatOfPoint> contours = new ArrayList<>();
Mat hierarchy = new Mat();
// find contours
Imgproc.findContours(maskedImage, contours, hierarchy, Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_SIMPLE);
// if any contour exist...
if (hierarchy.size().height > 0 && hierarchy.size().width > 0)
{
// for each contour, display it in blue
for (int idx = 0; idx >= 0; idx = (int) hierarchy.get(0, idx)[0])
{
Imgproc.drawContours(frame, contours, idx, new Scalar(250, 0, 0));
}
}
return frame;
}
/**
* Set typical {@link ImageView} properties: a fixed width and the
* information to preserve the original image ration
*
* @param image
* the {@link ImageView} to use
* @param dimension
* the width of the image to set
*/
private void imageViewProperties(ImageView image, int dimension)
{
// set a fixed width for the given ImageView
image.setFitWidth(dimension);
// preserve the image ratio
image.setPreserveRatio(true);
}
/**
* Stop the acquisition from the camera and release all the resources
*/
private void stopAcquisition()
{
if (this.timer!=null && !this.timer.isShutdown())
{
try
{
// stop the timer
this.timer.shutdown();
this.timer.awaitTermination(33, TimeUnit.MILLISECONDS);
}
catch (InterruptedException e)
{
// log any exception
System.err.println("Exception in stopping the frame capture, trying to release the camera now... " + e);
}
}
if (this.capture.isOpened())
{
// release the camera
this.capture.release();
}
}
/**
* Update the {@link ImageView} in the JavaFX main thread
*
* @param view
* the {@link ImageView} to update
* @param image
* the {@link Image} to show
*/
private void updateImageView(ImageView view, Image image)
{
Utils.onFXThread(view.imageProperty(), image);
}
/**
* On application close, stop the acquisition from the camera
*/
protected void setClosed()
{
this.stopAcquisition();
}
}