├── .classpath
├── .gitignore
├── .project
├── README.md
├── build.fxbuild
├── screenshots
├── one-tennis-ball.png
└── two-tennis-balls.png
└── src
└── it
└── polito
├── elite
└── teaching
│ └── cv
│ └── utils
│ └── Utils.java
└── teaching
└── cv
├── ObjRecognition.fxml
├── ObjRecognition.java
├── ObjRecognitionController.java
└── application.css
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /bin
2 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | Lab7
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.xtext.ui.shared.xtextBuilder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.xtext.ui.shared.xtextNature
21 | org.eclipse.jdt.core.javanature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Object Recognition with OpenCV and JavaFX
2 |
3 | [](http://unmaintained.tech/)
4 |
5 | *Computer Vision course - [Politecnico di Torino](http://www.polito.it)*
6 |
7 | A project, made in Eclipse (Neon), for identify and track one or more tennis balls. It performs the detection of the tennis balls upon a webcam video stream by using the color range of the balls, erosion and dilation, and the `findContours` method. Some screenshots of the running project are available in the `screenshots` folder.
8 |
9 | Please, note that the project is an Eclipse project, made for teaching purposes. Before using it, you need to install the OpenCV library (version 3.x) and JavaFX 8 and create a `User Library` named `opencv` that links to the OpenCV jar and native libraries.
10 |
11 | A guide for getting started with OpenCV and Java is available at [http://opencv-java-tutorials.readthedocs.org/en/latest/index.html](http://opencv-java-tutorials.readthedocs.org/en/latest/index.html).
12 |
--------------------------------------------------------------------------------
/build.fxbuild:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/screenshots/one-tennis-ball.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opencv-java/object-detection/7f96acce8265cef673788186d2323519b09c20fe/screenshots/one-tennis-ball.png
--------------------------------------------------------------------------------
/screenshots/two-tennis-balls.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opencv-java/object-detection/7f96acce8265cef673788186d2323519b09c20fe/screenshots/two-tennis-balls.png
--------------------------------------------------------------------------------
/src/it/polito/elite/teaching/cv/utils/Utils.java:
--------------------------------------------------------------------------------
1 | package it.polito.elite.teaching.cv.utils;
2 |
3 | import java.awt.image.BufferedImage;
4 | import java.awt.image.DataBufferByte;
5 |
6 | import org.opencv.core.Mat;
7 |
8 | import javafx.application.Platform;
9 | import javafx.beans.property.ObjectProperty;
10 | import javafx.embed.swing.SwingFXUtils;
11 | import javafx.scene.image.Image;
12 |
13 | /**
14 | * Provide general purpose methods for handling OpenCV-JavaFX data conversion.
15 | * Moreover, expose some "low level" methods for matching few JavaFX behavior.
16 | *
17 | * @author Luigi De Russis
18 | * @author Maximilian Zuleger
19 | * @version 1.0 (2016-09-17)
20 | * @since 1.0
21 | *
22 | */
23 | public final class Utils
24 | {
25 | /**
26 | * Convert a Mat object (OpenCV) in the corresponding Image for JavaFX
27 | *
28 | * @param frame
29 | * the {@link Mat} representing the current frame
30 | * @return the {@link Image} to show
31 | */
32 | public static Image mat2Image(Mat frame)
33 | {
34 | try
35 | {
36 | return SwingFXUtils.toFXImage(matToBufferedImage(frame), null);
37 | }
38 | catch (Exception e)
39 | {
40 | System.err.println("Cannot convert the Mat obejct: " + e);
41 | return null;
42 | }
43 | }
44 |
45 | /**
46 | * Generic method for putting element running on a non-JavaFX thread on the
47 | * JavaFX thread, to properly update the UI
48 | *
49 | * @param property
50 | * a {@link ObjectProperty}
51 | * @param value
52 | * the value to set for the given {@link ObjectProperty}
53 | */
54 | public static void onFXThread(final ObjectProperty property, final T value)
55 | {
56 | Platform.runLater(() -> {
57 | property.set(value);
58 | });
59 | }
60 |
61 | /**
62 | * Support for the {@link mat2image()} method
63 | *
64 | * @param original
65 | * the {@link Mat} object in BGR or grayscale
66 | * @return the corresponding {@link BufferedImage}
67 | */
68 | private static BufferedImage matToBufferedImage(Mat original)
69 | {
70 | // init
71 | BufferedImage image = null;
72 | int width = original.width(), height = original.height(), channels = original.channels();
73 | byte[] sourcePixels = new byte[width * height * channels];
74 | original.get(0, 0, sourcePixels);
75 |
76 | if (original.channels() > 1)
77 | {
78 | image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
79 | }
80 | else
81 | {
82 | image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
83 | }
84 | final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
85 | System.arraycopy(sourcePixels, 0, targetPixels, 0, sourcePixels.length);
86 |
87 | return image;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/it/polito/teaching/cv/ObjRecognition.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/it/polito/teaching/cv/ObjRecognition.java:
--------------------------------------------------------------------------------
1 | package it.polito.teaching.cv;
2 |
3 | import org.opencv.core.Core;
4 |
5 | import javafx.application.Application;
6 | import javafx.event.EventHandler;
7 | import javafx.stage.Stage;
8 | import javafx.stage.WindowEvent;
9 | import javafx.scene.Scene;
10 | import javafx.scene.layout.BorderPane;
11 | import javafx.fxml.FXMLLoader;
12 |
13 | public class ObjRecognition extends Application
14 | {
15 | /**
16 | * The main class for a JavaFX application. It creates and handles the main
17 | * window with its resources (style, graphics, etc.).
18 | *
19 | * This application looks for any tennis ball in the camera video stream and
20 | * try to select them according to their HSV values. Found tennis balls are
21 | * framed with a blue line.
22 | *
23 | * @author Luigi De Russis
24 | * @version 2.0 (2017-03-10)
25 | * @since 1.0 (2015-01-13)
26 | *
27 | */
28 | @Override
29 | public void start(Stage primaryStage)
30 | {
31 | try
32 | {
33 | // load the FXML resource
34 | FXMLLoader loader = new FXMLLoader(getClass().getResource("ObjRecognition.fxml"));
35 | // store the root element so that the controllers can use it
36 | BorderPane root = (BorderPane) loader.load();
37 | // set a whitesmoke background
38 | root.setStyle("-fx-background-color: whitesmoke;");
39 | // create and style a scene
40 | Scene scene = new Scene(root, 800, 600);
41 | scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
42 | // create the stage with the given title and the previously created
43 | // scene
44 | primaryStage.setTitle("Object Recognition");
45 | primaryStage.setScene(scene);
46 | // show the GUI
47 | primaryStage.show();
48 |
49 | // set the proper behavior on closing the application
50 | ObjRecognitionController controller = loader.getController();
51 | primaryStage.setOnCloseRequest((new EventHandler() {
52 | public void handle(WindowEvent we)
53 | {
54 | controller.setClosed();
55 | }
56 | }));
57 | }
58 | catch (Exception e)
59 | {
60 | e.printStackTrace();
61 | }
62 | }
63 |
64 | public static void main(String[] args)
65 | {
66 | // load the native OpenCV library
67 | System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
68 |
69 | launch(args);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/it/polito/teaching/cv/ObjRecognitionController.java:
--------------------------------------------------------------------------------
1 | package it.polito.teaching.cv;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.concurrent.Executors;
6 | import java.util.concurrent.ScheduledExecutorService;
7 | import java.util.concurrent.TimeUnit;
8 |
9 | import org.opencv.core.Core;
10 | import org.opencv.core.Mat;
11 | import org.opencv.core.MatOfPoint;
12 | import org.opencv.core.Scalar;
13 | import org.opencv.core.Size;
14 | import org.opencv.imgproc.Imgproc;
15 | import org.opencv.videoio.VideoCapture;
16 |
17 | import it.polito.elite.teaching.cv.utils.Utils;
18 | import javafx.beans.property.ObjectProperty;
19 | import javafx.beans.property.SimpleObjectProperty;
20 | import javafx.fxml.FXML;
21 | import javafx.scene.control.Button;
22 | import javafx.scene.control.Label;
23 | import javafx.scene.control.Slider;
24 | import javafx.scene.image.Image;
25 | import javafx.scene.image.ImageView;
26 |
27 | /**
28 | * The controller associated with the only view of our application. The
29 | * application logic is implemented here. It handles the button for
30 | * starting/stopping the camera, the acquired video stream, the relative
31 | * controls and the image segmentation process.
32 | *
33 | * @author Luigi De Russis
34 | * @version 2.0 (2017-03-10)
35 | * @since 1.0 (2015-01-13)
36 | *
37 | */
38 | public class ObjRecognitionController
39 | {
40 | // FXML camera button
41 | @FXML
42 | private Button cameraButton;
43 | // the FXML area for showing the current frame
44 | @FXML
45 | private ImageView originalFrame;
46 | // the FXML area for showing the mask
47 | @FXML
48 | private ImageView maskImage;
49 | // the FXML area for showing the output of the morphological operations
50 | @FXML
51 | private ImageView morphImage;
52 | // FXML slider for setting HSV ranges
53 | @FXML
54 | private Slider hueStart;
55 | @FXML
56 | private Slider hueStop;
57 | @FXML
58 | private Slider saturationStart;
59 | @FXML
60 | private Slider saturationStop;
61 | @FXML
62 | private Slider valueStart;
63 | @FXML
64 | private Slider valueStop;
65 | // FXML label to show the current values set with the sliders
66 | @FXML
67 | private Label hsvCurrentValues;
68 |
69 | // a timer for acquiring the video stream
70 | private ScheduledExecutorService timer;
71 | // the OpenCV object that performs the video capture
72 | private VideoCapture capture = new VideoCapture();
73 | // a flag to change the button behavior
74 | private boolean cameraActive;
75 |
76 | // property for object binding
77 | private ObjectProperty hsvValuesProp;
78 |
79 | /**
80 | * The action triggered by pushing the button on the GUI
81 | */
82 | @FXML
83 | private void startCamera()
84 | {
85 | // bind a text property with the string containing the current range of
86 | // HSV values for object detection
87 | hsvValuesProp = new SimpleObjectProperty<>();
88 | this.hsvCurrentValues.textProperty().bind(hsvValuesProp);
89 |
90 | // set a fixed width for all the image to show and preserve image ratio
91 | this.imageViewProperties(this.originalFrame, 400);
92 | this.imageViewProperties(this.maskImage, 200);
93 | this.imageViewProperties(this.morphImage, 200);
94 |
95 | if (!this.cameraActive)
96 | {
97 | // start the video capture
98 | this.capture.open(0);
99 |
100 | // is the video stream available?
101 | if (this.capture.isOpened())
102 | {
103 | this.cameraActive = true;
104 |
105 | // grab a frame every 33 ms (30 frames/sec)
106 | Runnable frameGrabber = new Runnable() {
107 |
108 | @Override
109 | public void run()
110 | {
111 | // effectively grab and process a single frame
112 | Mat frame = grabFrame();
113 | // convert and show the frame
114 | Image imageToShow = Utils.mat2Image(frame);
115 | updateImageView(originalFrame, imageToShow);
116 | }
117 | };
118 |
119 | this.timer = Executors.newSingleThreadScheduledExecutor();
120 | this.timer.scheduleAtFixedRate(frameGrabber, 0, 33, TimeUnit.MILLISECONDS);
121 |
122 | // update the button content
123 | this.cameraButton.setText("Stop Camera");
124 | }
125 | else
126 | {
127 | // log the error
128 | System.err.println("Failed to open the camera connection...");
129 | }
130 | }
131 | else
132 | {
133 | // the camera is not active at this point
134 | this.cameraActive = false;
135 | // update again the button content
136 | this.cameraButton.setText("Start Camera");
137 |
138 | // stop the timer
139 | this.stopAcquisition();
140 | }
141 | }
142 |
143 | /**
144 | * Get a frame from the opened video stream (if any)
145 | *
146 | * @return the {@link Image} to show
147 | */
148 | private Mat grabFrame()
149 | {
150 | Mat frame = new Mat();
151 |
152 | // check if the capture is open
153 | if (this.capture.isOpened())
154 | {
155 | try
156 | {
157 | // read the current frame
158 | this.capture.read(frame);
159 |
160 | // if the frame is not empty, process it
161 | if (!frame.empty())
162 | {
163 | // init
164 | Mat blurredImage = new Mat();
165 | Mat hsvImage = new Mat();
166 | Mat mask = new Mat();
167 | Mat morphOutput = new Mat();
168 |
169 | // remove some noise
170 | Imgproc.blur(frame, blurredImage, new Size(7, 7));
171 |
172 | // convert the frame to HSV
173 | Imgproc.cvtColor(blurredImage, hsvImage, Imgproc.COLOR_BGR2HSV);
174 |
175 | // get thresholding values from the UI
176 | // remember: H ranges 0-180, S and V range 0-255
177 | Scalar minValues = new Scalar(this.hueStart.getValue(), this.saturationStart.getValue(),
178 | this.valueStart.getValue());
179 | Scalar maxValues = new Scalar(this.hueStop.getValue(), this.saturationStop.getValue(),
180 | this.valueStop.getValue());
181 |
182 | // show the current selected HSV range
183 | String valuesToPrint = "Hue range: " + minValues.val[0] + "-" + maxValues.val[0]
184 | + "\tSaturation range: " + minValues.val[1] + "-" + maxValues.val[1] + "\tValue range: "
185 | + minValues.val[2] + "-" + maxValues.val[2];
186 | Utils.onFXThread(this.hsvValuesProp, valuesToPrint);
187 |
188 | // threshold HSV image to select tennis balls
189 | Core.inRange(hsvImage, minValues, maxValues, mask);
190 | // show the partial output
191 | this.updateImageView(this.maskImage, Utils.mat2Image(mask));
192 |
193 | // morphological operators
194 | // dilate with large element, erode with small ones
195 | Mat dilateElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(24, 24));
196 | Mat erodeElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(12, 12));
197 |
198 | Imgproc.erode(mask, morphOutput, erodeElement);
199 | Imgproc.erode(morphOutput, morphOutput, erodeElement);
200 |
201 | Imgproc.dilate(morphOutput, morphOutput, dilateElement);
202 | Imgproc.dilate(morphOutput, morphOutput, dilateElement);
203 |
204 | // show the partial output
205 | this.updateImageView(this.morphImage, Utils.mat2Image(morphOutput));
206 |
207 | // find the tennis ball(s) contours and show them
208 | frame = this.findAndDrawBalls(morphOutput, frame);
209 |
210 | }
211 |
212 | }
213 | catch (Exception e)
214 | {
215 | // log the (full) error
216 | System.err.print("Exception during the image elaboration...");
217 | e.printStackTrace();
218 | }
219 | }
220 |
221 | return frame;
222 | }
223 |
224 | /**
225 | * Given a binary image containing one or more closed surfaces, use it as a
226 | * mask to find and highlight the objects contours
227 | *
228 | * @param maskedImage
229 | * the binary image to be used as a mask
230 | * @param frame
231 | * the original {@link Mat} image to be used for drawing the
232 | * objects contours
233 | * @return the {@link Mat} image with the objects contours framed
234 | */
235 | private Mat findAndDrawBalls(Mat maskedImage, Mat frame)
236 | {
237 | // init
238 | List contours = new ArrayList<>();
239 | Mat hierarchy = new Mat();
240 |
241 | // find contours
242 | Imgproc.findContours(maskedImage, contours, hierarchy, Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_SIMPLE);
243 |
244 | // if any contour exist...
245 | if (hierarchy.size().height > 0 && hierarchy.size().width > 0)
246 | {
247 | // for each contour, display it in blue
248 | for (int idx = 0; idx >= 0; idx = (int) hierarchy.get(0, idx)[0])
249 | {
250 | Imgproc.drawContours(frame, contours, idx, new Scalar(250, 0, 0));
251 | }
252 | }
253 |
254 | return frame;
255 | }
256 |
257 | /**
258 | * Set typical {@link ImageView} properties: a fixed width and the
259 | * information to preserve the original image ration
260 | *
261 | * @param image
262 | * the {@link ImageView} to use
263 | * @param dimension
264 | * the width of the image to set
265 | */
266 | private void imageViewProperties(ImageView image, int dimension)
267 | {
268 | // set a fixed width for the given ImageView
269 | image.setFitWidth(dimension);
270 | // preserve the image ratio
271 | image.setPreserveRatio(true);
272 | }
273 |
274 | /**
275 | * Stop the acquisition from the camera and release all the resources
276 | */
277 | private void stopAcquisition()
278 | {
279 | if (this.timer!=null && !this.timer.isShutdown())
280 | {
281 | try
282 | {
283 | // stop the timer
284 | this.timer.shutdown();
285 | this.timer.awaitTermination(33, TimeUnit.MILLISECONDS);
286 | }
287 | catch (InterruptedException e)
288 | {
289 | // log any exception
290 | System.err.println("Exception in stopping the frame capture, trying to release the camera now... " + e);
291 | }
292 | }
293 |
294 | if (this.capture.isOpened())
295 | {
296 | // release the camera
297 | this.capture.release();
298 | }
299 | }
300 |
301 | /**
302 | * Update the {@link ImageView} in the JavaFX main thread
303 | *
304 | * @param view
305 | * the {@link ImageView} to update
306 | * @param image
307 | * the {@link Image} to show
308 | */
309 | private void updateImageView(ImageView view, Image image)
310 | {
311 | Utils.onFXThread(view.imageProperty(), image);
312 | }
313 |
314 | /**
315 | * On application close, stop the acquisition from the camera
316 | */
317 | protected void setClosed()
318 | {
319 | this.stopAcquisition();
320 | }
321 |
322 | }
323 |
--------------------------------------------------------------------------------
/src/it/polito/teaching/cv/application.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/opencv-java/object-detection/7f96acce8265cef673788186d2323519b09c20fe/src/it/polito/teaching/cv/application.css
--------------------------------------------------------------------------------