본문 바로가기

DEV

3. Your First JavaFX Application with OpenCV (OpenCV를 사용한 첫 번째 JavaFX 응용 프로그램)

Your First JavaFX Application with OpenCV

OpenCV를 사용한 첫 번째 JavaFX 응용 프로그램
저와 같은 영어 까막누니 분들께 이 글을 바칩니다. 번역은 구글 번역기 돌렸습니다.

Note

We assume that by now you have already read the previous tutorials. If not, please check previous tutorials at http://opencv-java-tutorials.readthedocs.org/en/latest/index.html. You can also find the source code and resources at https://github.com/opencv-java/

우리는 지금까지 이미 이전 튜토리얼을 읽었다 고 가정합니다. 그렇지 않은 경우 http://opencv-java-tutorials.readthedocs.org/en/latest/index.html에서 이전 자습서를 확인하십시오. https://github.com/opencv-java/에서 소스 코드와 리소스를 찾을 수도 있습니다.

A JavaFX application with OpenCV

OpenCV가있는 JavaFX 응용 프로그램

This tutorial will guide you through the creation of a simple JavaFX GUI application using the OpenCV library in Eclipse.

이 튜토리얼은 Eclipse에서 OpenCV 라이브러리를 사용하여 간단한 JavaFX GUI 애플리케이션을 작성하는 방법을 안내합니다.

What we will do in this tutorial

이 자습서에서 수행 할 작업

In this guide, we will:
  • Install the e(fx)clipse plugin and (optionally) Scene Builder.
  • Work with Scene Builder.
  • Write and Run our application.

이 가이드에서 우리는 :
  • e (fx) clipse 플러그인과 (선택적으로) Scene Builder를 설치하십시오.
  • Scene Builder로 작업하십시오.
  • 우리의 응용 프로그램을 작성하고 실행하십시오.

Your First Application in JavaFX

JavaFX에서의 첫 번째 애플리케이션

The application you will write by following this tutorial is going to capture a video stream from a webcam and, then, it will display it on the user interface (GUI). We will create the GUI with Scene Builder: it is will have a button, which will allow us to start and stop the stream, and a simple image view container where we will put each stream frame.

이 자습서를 따라 작성하는 응용 프로그램은 웹캠에서 비디오 스트림을 캡처 한 다음 사용자 인터페이스 (GUI)에 표시합니다. 우리는 Scene Builder로 GUI를 만들 것입니다 : 그것은 우리가 스트림을 시작하고 멈출 수있는 버튼과 우리가 각 스트림 프레임을 넣을 간단한 이미지 뷰 컨테이너를 가질 것입니다.

Installing e(fx)clipse plugin and Scene Builder

e (fx) clipse 플러그인 및 씬 빌더 설치하기

In Eclipse, install the e(fx)clipse plugin, by following the guide at http://www.eclipse.org/efxclipse/install.html#fortheambitious. If you choose not to install such a plugin, you have to create a traditional Java project, only. Download and install JavaFX Scene Builder 2.0 from http://www.oracle.com/technetwork/java/javafxscenebuilder-1x-archive-2199384.html.

Eclipse에서 http://www.eclipse.org/efxclipse/install.html#fortheambitious의 가이드에 따라 e (fx) 클립se 플러그인을 설치하십시오. 
그러한 플러그인을 설치하지 않기로 결정했다면, 전통적인 Java 프로젝트 만 생성해야합니다. 

직접 실행해보면 찍은 스크린샷을 참고하시라고 첨부합니다.


http://www.oracle.com/technetwork/java/javafxscenebuilder-1x-archive-2199384.html에서 JavaFX Scene Builder 2.0을 다운로드하여 설치하십시오.

fx 설치 시참고할 만한 사이트를 링크합니다.
http://code.makery.ch/library/javafx-8-tutorial/kr/part1/


Now you can create a new JavaFX project. Go to File > New > Project... and select JavaFX project....

이제 새로운 JavaFX 프로젝트를 만들 수 있습니다. 파일> 새로 만들기> 프로젝트 ...로 이동하여 JavaFX 프로젝트 ...를 선택합니다.
_images/03-00.png
주의 !! : 일반 java project 가 아닌 JavaFX project 입니다.

Choose a name for your project and click Next.

프로젝트의 이름을 선택하고 다음을 클릭하십시오.
_images/03-01.png

Now add your OpenCV user library to your project and click Next.

이제 OpenCV 사용자 라이브러리를 프로젝트에 추가하고 다음을 클릭하십시오.
_images/03-02.png

Choose a name for your package, for the FXML file and for the Controller Class. The FXML file will contain the description of your GUI in FXML language, while the Controller Class will handle all the method and event which have to be called and managed when the user interacts with the GUI’s components.

패키지 이름, FXML 파일 및 컨트롤러 클래스를 선택하십시오. FXML 파일에는 GUI에 대한 설명이 FXML 언어로 포함되며 Controller 클래스는 사용자가 GUI 구성 요소와 상호 작용할 때 호출 및 관리해야하는 모든 메서드와 이벤트를 처리합니다.
_images/03-03.png

Working with Scene Builder

장면 작성기로 작업하기

If you have installed Scene Builder you can now right click on your FXML file in Eclipse and select Open with SceneBuilderScene Builder can help construct you gui by interacting with a graphic interface; this allows you to see a real time preview of your window and modify your components and their position just by editing the graphic preview. Let’s take a look at what I’m talking about. At fist the FXML file will have just an AnchorPane. An AnchorPane allows the edges of child nodes to be anchored to an offset from the anchorpane’s edges. If the anchorpane has a border and/or padding set, the offsets will be measured from the inside edge of those insets. The anchorpane lays out each managed child regardless of the child’s visible property value; unmanaged children are ignored for all layout calculations. You can go ahead and delete the anchorpane and add a BorderPane instead. A BorderPane lays out children in top, left, right, bottom, and center positions.

Scene Builder를 설치했다면 Eclipse에서 FXML 파일을 마우스 오른쪽 버튼으로 클릭하고 SceneBuilder로 열기를 선택할 수 있습니다. Scene Builder는 그래픽 인터페이스와 상호 작용하여 GUI를 구성 할 수 있습니다. 이렇게하면 그래픽 미리보기를 편집하여 윈도우의 실시간 미리보기를보고 구성 요소와 위치를 수정할 수 있습니다. 제가 무슨 말을하는지 봅시다. fist에서 FXML 파일은 AnchorPane 만 가질 것입니다. AnchorPane는, 아이 노드의 엣지를 엥커 팬의 엣지로부터의 오프셋 (offset)에 엥커하는 것을 가능하게합니다. 앵커 판에 보더 및 / 또는 패딩 세트가있는 경우, 그 인 세트의 안쪽 가장자리로부터의 오프셋이 측정됩니다. 앵커 팬은 자식의 표시 속성 값에 관계없이 관리되는 각 자식을 배치합니다. 관리되지 않는 자식은 모든 레이아웃 계산에서 무시됩니다. 앵커 패널을 삭제하고 대신 BorderPane을 추가 할 수 있습니다. BorderPane는 상하 좌우의 아이를 레이아웃합니다.

Opening the file with SceneBuilder failed. Try setting the absolute path to scenebuilder in your the preferences

SceneBuilder로 파일을 열지 못했습니다. 환경 설정에서 씬 빌더의 절대 경로를 설정해보십시오.

만약 위와같은 메시지가 나온다면 .. 저의 경우 http://www.oracle.com/technetwork/java/javafxscenebuilder-1x-archive-2199384.html 사이트에서 JavaFX Scene Builder 2.0 을 다운 받지 않아서 였습니다.


_images/03-04.png

You can add a BorderPane by dragging from the Container menu a borderpane and then drop it in the Hierarchy menu. Now we can add the button that will allow us to start and stop the stream. Take a button component from the Controls menu and drop it on the BOTTOM field of our BP. As we can see, on the right we will get three menus (Properties, Layout, Code) which are used to customize our selected component. For example we can change text of our button in “Start Camera” in the Text field under the Properties menu and the id of the button (e.g. “start_btn”) in the fx:id field under the Code menu.

컨테이너 메뉴에서 테두리 패널을 드래그하여 계층 구조 메뉴에 놓아서 BorderPane을 추가 할 수 있습니다. 이제 스트림을 시작하고 중지 할 수있는 버튼을 추가 할 수 있습니다. Controls 메뉴에서 버튼 구성 요소를 가져와 BP의 BOTTOM 필드에 놓으십시오. 보시다시피, 오른쪽에는 선택한 구성 요소를 사용자 정의하는 데 사용되는 세 가지 메뉴 (속성, 레이아웃, 코드)가 표시됩니다. 예를 들어 코드 메뉴의 fx : id 필드에서 속성 메뉴의 텍스트 필드에있는 "카메라 시작"과 버튼의 ID (예 : "start_btn")에서 버튼의 텍스트를 변경할 수 있습니다.
_images/03-05.png
_images/03-06.png

We are going to need the id of the button later, in order to edit the button properties from our Controller‘s methods. As you can see our button is too close to the edge of the windows, so we should add some bottom margin to it; to do so we can add this information in the Layout menu. In order to make the button work, we have to set the name of the method (e.g. “startCamera”) that will execute the action we want to preform in the field OnAction under the Code menu.

컨트롤러의 메소드에서 버튼 속성을 편집하려면 나중에 버튼의 ID가 필요합니다. 보시다시피 버튼이 창 가장자리에 너무 가깝기 때문에 아래쪽 여백을 추가해야합니다. 이렇게하려면 레이아웃 메뉴에서이 정보를 추가 할 수 있습니다. 버튼을 작동 시키려면 코드 메뉴의 OnAction 필드에서 수행 할 작업을 실행할 메서드 이름 (예 : "startCamera")을 설정해야합니다.
_images/03-07.png

Now, we shall add an ImageView component from the Controls menu into the CENTER field of our BP. Let’s also edit the id of the image view (e.g. “currentFrame”), and add some margin to it.

이제 BP의 CENTER 필드에 Controls 메뉴의 ImageView 컴포넌트를 추가합니다. 이미지보기의 ID (예 : 'currentFrame')를 수정하고 여백을 추가해 보겠습니다.
_images/03-08.png

Finally we have to tell which Controller class will mange the GUI, we can do so by adding our controller class name in the Controller class field under the Controller menu located in the bottom left corner of the window.

마지막으로 우리는 어떤 Controller 클래스가 GUI를 관리 할 것인지를 말해야합니다. 우리는 컨트롤러 클래스 필드에 컨트롤러 클래스 필드를 추가합니다. 컨트롤러 클래스 필드는 윈도우의 왼쪽 하단 모서리에 있습니다.

We just created our first GUI by using Scene Builder, if you save the file and return to Eclipse you will notice that some FXML code has been generated automatically.

Scene Builder를 사용하여 첫 번째 GUI를 만들었습니다. 파일을 저장하고 Eclipse로 돌아 가면 FXML 코드가 자동 생성됩니다.

Key Concepts in JavaFX

JavaFX의 주요 개념

The Stage is where the application will be displayed (e.g., a Windows’ window). A Scene is one container of Nodes that compose one “page” of your application. A Node is an element in the Scene, with a visual appearance and an interactive behavior. Nodes may be hierarchically nested . In the Main class we have to pass to the start function our primary stage:

스테이지는 애플리케이션이 표시되는 곳입니다 (예 : Windows의 창). Scene은 응용 프로그램의 "페이지"를 구성하는 노드 컨테이너입니다. 노드는 시각적 인 모양과 대화식 동작을 사용하여 장면의 요소입니다. 노드는 계층 적으로 중첩 될 수 있습니다. Main 클래스에서 start 함수에 전달해야하는 첫 번째 단계는 다음과 같습니다.
public void start(Stage primaryStage)

and load the fxml file that will populate our stage, the root element of the scene and the controller class:

무대, 장면의 루트 요소 및 컨트롤러 클래스를 채울 fxml 파일을로드하십시오.
FXMLLoader loader = new FXMLLoader(getClass().getResource("FXHelloCV.fxml"));
BorderPane root = (BorderPane) loader.load();
FXController controller = loader.getController();

Managing GUI Interactions With the Controller Class

컨트롤러 클래스와의 GUI 상호 작용 관리

For our application we need to do basically two thing: control the button push and the refreshment of the image view. To do so we have to create a reference between the gui components and a variable used in our controller class:

우리는 기본적으로 버튼 푸시 제어와 이미지 뷰의 리프레시 두 가지 작업을 수행해야합니다. 그렇게하기 위해서 우리는 gui 컴포넌트와 컨트롤러 클래스에서 사용되는 변수 사이에 레퍼런스를 생성해야합니다 :
@FXML
private Button button;
@FXML
private ImageView currentFrame;

The @FXML tag means that we are linking our variable to an element of the fxml file and the value used to declare the variable has to equal to the id set for that specific element.

@FXML 태그는 변수를 fxml 파일의 요소에 연결하고 변수를 선언하는 데 사용 된 값이 해당 특정 요소에 설정된 ID와 같아야 함을 의미합니다.

The @FXML tag is used with the same meaning for the Actions set under the Code menu in a specific element.

@FXML 태그는 특정 요소의 코드 메뉴에있는 액션 집합과 동일한 의미로 사용됩니다.

for:

<Button fx:id="button" mnemonicParsing="false" onAction="#startCamera" text="Start Camera" BorderPane.alignment="CENTER">

we set:

@FXML
protected void startCamera(ActionEvent event) { ...

Video Capturing

비디오 캡처

Essentially, all the functionalities required for video manipulation is integrated in the VideoCapture class.

기본적으로 비디오 조작에 필요한 모든 기능은 VideoCapture 클래스에 통합되어 있습니다.
private VideoCapture capture = new VideoCapture();

This on itself builds on the FFmpeg open source library. A video is composed of a succession of images, we refer to these in the literature as frames. In case of a video file there is a frame rate specifying just how long is between two frames. While for the video cameras usually there is a limit of just how many frames they can digitalize per second. In our case we set as frame rate 30 frames per sec. To do so we initialize a timer (i.e., a `ScheduledExecutorService`) that will open a background task every 33 milliseconds.

이것 자체는 FFmpeg 오픈 소스 라이브러리를 기반으로합니다. 비디오는 일련의 이미지들로 구성되어 있으며, 우리는 이들을 문헌들에서 프레임들이라고 부릅니다. 비디오 파일의 경우 두 프레임 사이의 길이를 지정하는 프레임 속도가 있습니다. 비디오 카메라의 경우 일반적으로 초당 디지털화 할 수있는 프레임 수의 제한이 있습니다. 우리의 경우 우리는 프레임 속도를 초당 30 프레임으로 설정했습니다. 이를 위해 33 밀리 초마다 백그라운드 작업을 열리는 타이머 (즉, ScheduledExecutorService)를 초기화합니다.
Runnable frameGrabber = new Runnable() { ... }
this.timer = Executors.newSingleThreadScheduledExecutor();
            this.timer.scheduleAtFixedRate(frameGrabber, 0, 33, TimeUnit.MILLISECONDS);

To check if the binding of the class to a video source was successful or not use the isOpenedfunction:

비디오 소스에 클래스의 바인딩이 성공했는지 또는 isOpenedfunction을 사용하지 않았는지 확인하려면 :
if (this.capture.isOpened()) { ... }

Closing the video is automatic when the objects destructor is called. However, if you want to close it before this you need to call its release function.

객체 소멸자가 호출되면 비디오를 닫습니다. 그러나 이전에 닫으려면 릴리스 함수를 호출해야합니다.
this.capture.release();

The frames of the video are just simple images. Therefore, we just need to extract them from the VideoCapture object and put them inside a Mat one.

비디오의 프레임은 단순한 이미지입니다. 따라서 VideoCapture 객체에서 추출하여 Mat 내부에 배치하면됩니다.
Mat frame = new Mat();

The video streams are sequential. You may get the frames one after another by the read or the overloaded >> operator.

비디오 스트림은 순차적입니다. >> 읽기 또는 오버로드 된 >> 연산자로 프레임을 차례로 가져올 수 있습니다.
this.capture.read(frame);

Now we are going to convert our image from BGR to Grayscale format. OpenCV has a really nice function to do this kind of transformations:

이제 이미지를 BGR에서 Grayscale 형식으로 변환 할 것입니다. OpenCV는 이런 종류의 변형을 수행하는 정말 좋은 기능을 가지고 있습니다 :
Imgproc.cvtColor(frame, frame, Imgproc.COLOR_BGR2GRAY);
As you can see, cvtColor takes as arguments:
  • a source image (frame)
  • a destination image (frame), in which we will save the converted image.
  • an additional parameter that indicates what kind of transformation will be performed. In this case we use COLOR_BGR2GRAY (because of imread has BGR default channel order in case of color images).
보시다시피, cvtColor는 인수를 취합니다.
- 소스 이미지 (프레임)
- 변환 된 이미지를 저장하는 대상 이미지 (프레임).
- 수행 할 변형의 종류를 나타내는 추가 매개 변수입니다. 이 경우 우리는 COLOR_BGR2GRAY를 사용합니다 (왜냐하면 imread는 컬러 이미지의 경우 BGR 기본 채널 순서를 갖기 때문입니다).

Now in order to put the captured frame into the ImageView we need to convert the Mat in a Image. We first create a buffer to store the Mat.

이제 캡쳐 된 프레임을 ImageView에 넣으려면 이미지의 매트를 변환해야합니다. 매트를 저장하기위한 버퍼를 먼저 생성합니다.
MatOfByte buffer = new MatOfByte();

Then we can put the frame into the buffer by using the imencode function:

그런 다음 imencode 함수를 사용하여 프레임을 버퍼에 넣을 수 있습니다.
Imgcodecs.imencode(".png", frame, buffer);

This encodes an image into a memory buffer. The function compresses the image and stores it in the memory buffer that is resized to fit the result.

이미지를 메모리 버퍼로 인코딩합니다. 이 함수는 이미지를 압축하여 결과에 맞게 크기가 조정 된 메모리 버퍼에 저장합니다.

Note

imencode returns single-row matrix of type CV_8UC1 that contains encoded image as array of bytes.

imencode는 인코딩 된 이미지를 바이트 배열로 포함하는 CV_8UC1 유형의 단일 행렬을 반환합니다.
It takes three parameters:
  • (”.png”) File extension that defines the output format.
  • (frame) Image to be written.
  • (buffer) Output buffer resized to fit the compressed image.
세 가지 매개 변수가 필요합니다.
 - ( ".png") 출력 형식을 정의하는 파일 확장명입니다.
 - (프레임) 쓸 이미지.
 - (버퍼) 압축 된 이미지에 맞게 출력 버퍼의 크기가 조정됩니다.

Once we filled the buffer we have to stream it into an Image by using ByteArrayInputStream:

버퍼를 채우고 나면 ByteArrayInputStream을 사용하여 이미지로 스트리밍해야합니다.
new Image(new ByteArrayInputStream(buffer.toArray()));

Now we can put the new image in the ImageView. With Java 1.8 we cannot perform an update of a GUI element in a thread that differs from the main thread; so we need to get the new frame in a second thread and refresh our ImageView in the main thread:

이제 ImageView에 새 이미지를 넣을 수 있습니다. Java 1.8에서는 주 스레드와 다른 스레드에서 GUI 요소를 업데이트 할 수 없습니다. 두 번째 스레드에서 새 프레임을 가져와 주 스레드에서 ImageView를 새로 고쳐야합니다.
Image imageToShow = grabFrame();
Platform.runLater(new Runnable() {
        @Override public void run() { currentFrame.setImage(imageToShow); }
});
_images/03-09.png

The source code of the entire tutorial is available on GitHub.

전체 자습서의 소스 코드는 GitHub에서 사용할 수 있습니다.


하 하 하

필자도 노트북을 사용하지만 노트북에 캠드라이버를 설치한 경험이 없다. 결국 소스는 구동되지만 카메라를 찾을 수 없어 Image 부분에 아무것도 출력되지 않는다.


이미지를 불러올 수는 없을까 찾아보니 괜찮은 블러그가 있어 링크 한다.

http://blog.daum.net/kurtjim/13688747

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package application;
 
import java.io.ByteArrayInputStream;
 
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.imgcodecs.Imgcodecs;
 
// 자바 라이브러리
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
 
public class Main extends Application {
 
    @Override
    public void start(Stage primaryStage) throws Exception {
        Stage stage = new Stage();
        Group root = new Group();
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
 
        Mat in_image = new Mat();
 
        // 이미지 불로오는 부분
 
        // in_image = Imgcodecs.imread("D:\\opencv_image_lib\\lena.png");
        in_image = Imgcodecs.imread("D:\\OpenCv\\220px-Lenna.png");
 
        // 문제는 바로 이 부분 Mat 으로 된 in_image의 경우 Javafx나 swt 등에서 바로 ImageView를 할 수 없기에
 
        // 다음과 같이 바꿔서 하시면 됩니다. 아래 2줄은 stackOverflow에서 참고하였음.
 
        MatOfByte byteMat = new MatOfByte();
 
        Imgcodecs.imencode(".png", in_image, byteMat);
 
        Image img = new Image(new ByteArrayInputStream(byteMat.toArray()));
 
        ImageView imageView = new ImageView();
 
        imageView.setImage(img);
 
        // -_- 귀찮아서 VBox 하나 만들어 이미지 올렸음돠..
 
        VBox vbox = new VBox();
 
        vbox.setAlignment(Pos.CENTER);
 
        vbox.getChildren().add(imageView);
 
        // 그래도 GUI 인데 EXIT 버튼 하나 달아주는 센스.
 
        Button btnExit = new Button("Exit");
 
        btnExit.setPrefHeight(30);
 
        btnExit.setMaxWidth(Double.MAX_VALUE);
 
        btnExit.setPadding(new Insets(10));
 
        btnExit.setOnAction(e -> System.exit(0));
 
        vbox.getChildren().add(btnExit);
 
        root.getChildren().add(vbox);
 
        Double width = img.getWidth();
 
        Double height = img.getHeight() + btnExit.getPrefHeight() + 5;
 
        // width 하고 Height에 값이 잘들어 있나 할려고 넣었음 없어도 되는 부분.
 
        System.out.println(width + " : " + height);
 
        Scene scene = new Scene(root, width, height);
 
        stage.setScene(scene);
 
        stage.show();
 
    }
 
    public static void main(String[] args) {
        launch(args);
    }
 
}
cs


주석을 보니 재미나신분 같다. 

작성일자가 "쿠루쿠루족장 2015.07.02 17:49"

오늘은 2017-12-17 ... 2년전... 뒤쳐져있다는 생각이 든다 ...


노트북 카메라 없이는 더이상의 진행은 무리이다 싶어 찾아보았다.

http://sunstar2.tistory.com/1009

ManyCam 설치를 하고난 뒤 알게 되었다. 나의 노트묵 카메라는 멀정하다 ... 그렇다면 ... 앜!!! 뭔가 빼먹었다는것이다.


tutolial 의 소스와 github 의 소스가 달라서 연결에 문제가 있었다. 이런 ... 오류코드로 알려주지 않아서 소스는 정상인줄 알았다 ;;


소스를 첨부한다.


Main.java

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
package application;
    
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 Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            // load the FXML resource
            FXMLLoader loader = new FXMLLoader(getClass().getResource("FisrtJFX.fxml"));
            // store the root element so that the controllers can use it
            BorderPane rootElement = (BorderPane) loader.load();
            // create and style a scene
            Scene scene = new Scene(rootElement, 800, 600);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            // create the stage with the given title and the previously created
            // scene
            primaryStage.setTitle("JavaFX meets OpenCV");
            primaryStage.setScene(scene);
            // show the GUI
            primaryStage.show();
            
            // set the proper behavior on closing the application
            FXController 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);
    }
}
 
cs


FXController.java

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package application;
 
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
 
import org.opencv.core.Mat;
import org.opencv.imgproc.Imgproc;
import org.opencv.videoio.VideoCapture;
 
import application.utils.Utils;
//import it.polito.elite.teaching.cv.utils.Utils;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
 
public class FXController {
    
    // the FXML button
        @FXML
        private Button button;
        // the FXML image view
        @FXML
        private ImageView currentFrame;
        
        // a timer for acquiring the video stream
        private ScheduledExecutorService timer;
        // the OpenCV object that realizes the video capture
        private VideoCapture capture = new VideoCapture();
        // a flag to change the button behavior
        private boolean cameraActive = false;
        // the id of the camera to be used
        private static int cameraId = 0;
        
        /**
         * The action triggered by pushing the button on the GUI
         *
         * @param event
         *            the push button event
         */
        @FXML
        protected void startCamera(ActionEvent event)
        {
            if (!this.cameraActive)
            {
                // start the video capture
                this.capture.open(cameraId);
                
                // 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(currentFrame, imageToShow);
                        }
                    };
                    
                    this.timer = Executors.newSingleThreadScheduledExecutor();
                    this.timer.scheduleAtFixedRate(frameGrabber, 0, 33, TimeUnit.MILLISECONDS);
                    
                    // update the button content
                    this.button.setText("Stop Camera");
                }
                else
                {
                    // log the error
                    System.err.println("Impossible to open the camera connection...");
                }
            }
            else
            {
                // the camera is not active at this point
                this.cameraActive = false;
                // update again the button content
                this.button.setText("Start Camera");
                
                // stop the timer
                this.stopAcquisition();
            }
        }
        
        /**
         * Get a frame from the opened video stream (if any)
         *
         * @return the {@link Mat} to show
         */
        private Mat grabFrame()
        {
            // init everything
            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())
                    {
                        Imgproc.cvtColor(frame, frame, Imgproc.COLOR_BGR2GRAY);
                    }
                    
                }
                catch (Exception e)
                {
                    // log the error
                    System.err.println("Exception during the image elaboration: " + e);
                }
            }
            
            return frame;
        }
        
        /**
         * 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();
        }
}
 
cs



FisrtJFX.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
<?xml version="1.0" encoding="UTF-8"?>
 
<?import javafx.scene.image.*?>
<?import javafx.geometry.*?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.BorderPane?>
 
<BorderPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.FXController">
   <bottom>
      <Button fx:id="button" mnemonicParsing="false" onAction="#startCamera" text="Start Camera" BorderPane.alignment="CENTER">
         <BorderPane.margin>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
         </BorderPane.margin>
      </Button>
   </bottom>
   <padding>
      <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
   </padding>
   <center>
      <ImageView fx:id="currentFrame" fitHeight="500.0" fitWidth="500.0" pickOnBounds="true" preserveRatio="true" BorderPane.alignment="CENTER">
         <BorderPane.margin>
            <Insets />
         </BorderPane.margin>
      </ImageView>
   </center>
</BorderPane>
 
cs