Wednesday, January 8, 2014

Building OpenCV 2.4.8 with CUDA and running GPU methods in Java using JNI

Though OpenCV comes with Java bindings since version 2.4.4, there are still some modules that don't have Java bindings - the most obvious one being the gpu module.

The OpenCV distribution for Windows is not built with CUDA so the gpu module doesn't work out of the box. So the first step is to build the OpenCV binaries with CUDA. As there are no Java bindings for the gpu module, a Java application wanting to use gpu methods will need to use JNI (Java Native Interface) to talk to the OpenCV gpu DLL. This is described in a complete example at the end of this post.



Software used

OpenCV 2.4.8 Windows distribution
CUDA 5.5 Toolkit for Windows distribution (cuda_5.5.20_winvista_win7_win8_notebook_64.exe in my case)
CMake 2.8.12.1 - to configure and generate the make files.
Visual Studio 2010 - to build the OpenCV binaries and to build the sample native DLL.
Java Development Kit (JDK) 1.7.0_45, 64 bit
NetBeans 7.3 - to build the Java application that uses the OpenCV gpu methods.

The Operating System (OS) is Windows 7 Home Premium, 64 bit.



Configuring the build

Start CMake as administrator.



Enter the path to the OpenCV source. e.g. D:\PROJECTS\LIBRARIES\c\opencv\sources
and path where you want to place the binaries e.g. D:/PROJECTS/LIBRARIES/c/opencv/build_custom

Expand the BUILD group and ensure that BUILD_opencv_gpu is checked.



Expand the CUDA group and set CUDA_ARCH_BIN to the compute architecture of your GPU device. Note: if compute arch of the device is 2.1 then you must set this entry to 2.0 instead. Setting the specific arch of the device will save time while building the binaries. The rest of the entries in my build's CUDA group look like figure below.



Expand the WITH group and select WITH_CUDA.


Check/uncheck entries in all the other groups, as required.

Click the Configure button. CMake will check for required files and write output to its console. If it doesn't find any files then it highlights corresponding entries in red. Rectify the items marked in red and press Configure again. The console output should confirm that the gpu module will be built.

Press Generate button. This will generate all the make files required by Visual Studio 2010 to build the binaries.


Building OpenCV Binaries



Select the required solution platform (Win32 or x64). If you are using 64 bit Java and CUDA then the OpenCV binaries will also need to be 64 bit. Select the required configuration i.e.Debug or Release.

Build the solution by pressing F7. It may take some time to build all the projects. 

The OpenCV  DLLs will be created in the bin/Debug or bin/Release subdirectory on the build directory (e.g. D:\PROJECTS\LIBRARIES\c\opencv\build_custom\bin\Debug).  Add this path to the PATH environment variable.



Invoking OpenCV GPU methods from Java Application using JNI

Create a New Java Application project in NetBeans 7.3 IDE.  Write sample code to call a C++ native method containing OpenCV GPU code. The C++ code is contained in a native DLL. The Java code should contain the declaration of the native method. Build the Java application in NetBeans (Ctrl + F11). The compiled classes are in the build/classes subdirectory of the project.

Example Java code below.


 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
package testopencvjni;

/**
 * This Java application calls methods in native DLL that contain OpenCV GPU methods, via JNI.
 * @author faram
 */
public class TestOpenCVJni {

    // Declaration of the C++ native method containing OpenCV GPU code.
    // This particular method loads given image and shows it in a window then performs edge-detection
    // on it and shows it in another window.
    native void edgeFilter(String name);
    
    static {
        // Load the native DLL containing the OpenCV GPU code
        System.load("D:/PROJECTS/VisualStudio2010/TestOpenCVGpuLib/x64/Debug/TestOpenCVGpuLib.dll");
    }
    
    
    public static void main(String[] args) {
        String imageFile = "D:/PROJECTS/LIBRARIES/c/opencv/baboon.jpg";
        
        new TestOpenCVJni().edgeFilter(imageFile);
        
        
    }
}


The next step is to create a header file containing JNI declarations for the native method. This is done with the javah utility. cd into the project folder and run javah like -


javah  -o testcv.h  -classpath build/classes   testopencvjni.TestOpenCVJni



where

testcv.h is the JNI header file

testopencvjni is the Java package

TestOpenCVJni is the Java class containing the native method declaration.

All classes containing native method declarations should be listed in this command line.





The created JNI header file looks like -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class testopencvjni_TestOpenCVJni */

#ifndef _Included_testopencvjni_TestOpenCVJni
#define _Included_testopencvjni_TestOpenCVJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     testopencvjni_TestOpenCVJni
 * Method:    printHello
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_testopencvjni_TestOpenCVJni_edgeFilter
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif


The function name is of form Java_pkgName_ClassName_procName.  It is passed 2 required parameters -


env is a pointer to a JNI function table. Native functions can access java objects only via these JNI funtions.


obj refers to the Java object containing this native method. if the native method is declared static in the java class then obj refers to that class.


The function is also passed any other params listed in the native method declaration.


Now we need to create a C++ DLL project in Visual Studio 2010 which contains the definition of the native method declared in the JNI header file. The C++ source file should include the JNI header file (testcv.h) created previously


 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
// This is the main DLL file.

#include "stdafx.h"
#include "TestOpenCVGpuLib.h"

#include "D:\PROJECTS\NetBeans\TestOpenCVJni\testcv.h"  

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/gpu/gpu.hpp>

#include <stdio.h>  
#include <iostream>


using namespace cv;
using namespace std;

JNIEXPORT void JNICALL Java_testopencvjni_TestOpenCVJni_edgeFilter(JNIEnv *env, jobject obj, jstring name)
{
 // Convert JNI string to native string  
 const char *nativeName = env->GetStringUTFChars(name, 0);
 
 Mat colorImage = imread(nativeName);

 if (!colorImage.data) 
 {
  cout << "Unable to load image" << endl;
 }

 imshow("Original", colorImage);
 
 // GPU ops
 Mat src;
 // convert to grayscale image otherwise Canny will throw exception in
 // ...opencv\sources\modules\gpu\src\imgproc.cpp line 1558
 cvtColor(colorImage,src,CV_RGB2GRAY);
 
 gpu::GpuMat d_src(src);
 gpu::GpuMat d_dst;
 gpu::bilateralFilter(d_src, d_dst, -1, 50, 10);
 gpu::Canny(d_dst, d_dst, 10, 200, 3);
 Mat dst(d_dst);
 imshow("Edges", dst);
 
 waitKey(0);
 
 // release JNI resources
 env->ReleaseStringUTFChars(name, nativeName);
}

The above code loads image given by invoking Java application and displays it in a window and also does edge-detection on it and shows it in another window.

Build this project (F7) and note the path of the created DLL. This path should be put in the System.load() call in the invoking Java code.

Now back in NetBeans, press F6 to build and run the application.



References




Appendix

Screenshots of some CMake groups from my build -

Ungrouped Entries



BUILD group



ENABLE, INSTALL, JAVA , OPENCV, TBB groups





Resources

Some make files from my build -


Source code of the sample application -




6 comments:

  1. thanks for your post .It helped me a lot in doing my project . If possible could you mention how to build OpenCV 64 bit applications with Visual C++ 2010 Express in windows 8 OS

    ReplyDelete
    Replies
    1. I'm sorry i don't have a Windows 8 machine at present. I'll update the post when i get one. Also, you will need the full-fledged Visual Studio 2010 when building the OpenCV gpu module as it uses Cuda. See section 7 of this post http://faramblog.blogspot.in/2014/01/cuda-sdk-samples-vc-2010-express.html. I haven't been able to find a workaround yet.

      Delete
  2. Thank you for the tutorial! It will definitely help me with my thesis :-)

    ReplyDelete
  3. excellent tutorial. Much appreciated!

    ReplyDelete