0

I'm using this tutorial for JNI in Eclipse:

https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html#zz-2.6

(I'm using only the part "2.6 JNI in Eclipse").

The example in that tutorial (HelloJNI) worked for me, and also on my project.

Then I made one change to the header file- I added the line:

#include <vector>

And built it again by the instruction in the tutorial:

Run the makefile for the target "all", by right-click on the makefile ⇒ Make >Targets ⇒ Build ⇒ Select the target "all" ⇒ Build

And it replaced my header file with the original header file......

I don't understand why it happens, because the dependency of the target SPImageProc.h is SPImageProc.class, and I didn't change SPImageProc.class , I only changed SPImageProc.h.

Development Environment

+Eclipse IDE for Java Developers (32 bit) Version: Kepler Service Release 2.

+CDT plugin for Eclipse

+Windows 10 64-bit (I use eclipse 32-bit because at some point, the 64-bit eclipse couldn't open and the solution was to use 32-bit eclipse)

makefile

# Define a variable for classpath
CLASS_PATH = ../bin

# Define a virtual path for .class in the bin directory
vpath %.class $(CLASS_PATH)

all : spimageproc.dll

# $@ matches the target, $< matches the first dependency
spimageproc.dll : SPImageProc.o
    g++ -Wl,--add-stdcall-alias -shared -o $@ $<

# $@ matches the target, $< matches the first dependency
SPImageProc.o : SPImageProc.cpp SPImageProc.h
    g++ -I"C:\Program Files (x86)\Java\jdk1.8.0_212\include" -I"C:\Program Files (x86)\Java\jdk1.8.0_212\include\win32" -c $< -o $@

# $* matches the target filename without the extension
SPImageProc.h : SPImageProc.class
    javah -classpath $(CLASS_PATH) $*

clean :
    rm SPImageProc.h SPImageProc.o spimageproc.dll

SPImageProc.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class SPImageProc */

#ifndef _Included_SPImageProc
#define _Included_SPImageProc
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     SPImageProc
 * Method:    cppFunc
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_SPImageProc_cppFunc
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

SPImageProc.cpp

#include <jni.h>
#include <stdio.h>
#include "SPImageProc.h"

JNIEXPORT void JNICALL Java_SPImageProc_cppFunc(JNIEnv *env, jobject thisObj) {
   printf("After adding include vector to header !\n");
   return;
}

SPImageProc.java

public class SPImageProc {

    static {
        System.loadLibrary("spimageproc"); // spimageproc.dll

    }

    // Declare native method
    private native void cppFunc();

    public static void function() {
        new SPImageProc().cppFunc(); // Allocate an instance and invoke the native
                                    // method
    }
}

CBIR.java

public class CBIR {


   public static void main(String[] args) {
      SPImageProc.function();
   }

}

Edit

Those are the original files that I wanted to use(they are the reason that I decided to use jni):

SPImageProc.cpp

#include <cstdlib>
#include <cassert>
#include <cstring>
#include <opencv2/xfeatures2d.hpp>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <cstdio>
#include "SPImageProc.h"
extern "C" {
#include "SPLogger.h"
}

using namespace cv;
using namespace std;

#define PCA_MEAN_STR "mean"
#define PCA_EIGEN_VEC_STR "e_vectors"
#define PCA_EIGEN_VAL_STR "e_values"
#define STRING_LENGTH 1024
#define WARNING_MSG_LENGTH 2048

#define GENERAL_ERROR_MSG "An error occurred"
#define PCA_DIM_ERROR_MSG "PCA dimension couldn't be resolved"
#define PCA_FILE_NOT_EXIST "PCA file doesn't exist"
#define PCA_FILE_NOT_RESOLVED "PCA filename couldn't be resolved"
#define NUM_OF_IMAGES_ERROR "Number of images couldn't be resolved"
#define NUM_OF_FEATS_ERROR "Number of features couldn't be resolved"
#define MINIMAL_GUI_ERROR "Minimal GUI mode couldn't be resolved"
#define IMAGE_PATH_ERROR "Image path couldn't be resolved"
#define IMAGE_NOT_EXIST_MSG ": Images doesn't exist"
#define MINIMAL_GUI_NOT_SET_WARNING "Cannot display images in non-Minimal-GUI mode"
#define ALLOC_ERROR_MSG "Allocation error"
#define INVALID_ARG_ERROR "Invalid arguments"





void sp::ImageProc::initFromConfig(const SPConfig config) {
    SP_CONFIG_MSG msg = SP_CONFIG_SUCCESS;
    pcaDim = spConfigGetPCADim(config, &msg);
    if (msg != SP_CONFIG_SUCCESS) {
        spLoggerPrintError(PCA_DIM_ERROR_MSG, __FILE__, __func__, __LINE__);
        throw Exception();
    }
    numOfImages = spConfigGetNumOfImages(config, &msg);
    if (msg != SP_CONFIG_SUCCESS) {
        spLoggerPrintError(NUM_OF_IMAGES_ERROR, __FILE__, __func__, __LINE__);
        throw Exception();
    }
    numOfFeatures = spConfigGetNumOfFeatures(config, &msg);
    if (msg != SP_CONFIG_SUCCESS) {
        spLoggerPrintError(NUM_OF_FEATS_ERROR, __FILE__, __func__, __LINE__);
        throw Exception();
    }
    minimalGui = spConfigMinimalGui(config, &msg);
    if (msg != SP_CONFIG_SUCCESS) {
        spLoggerPrintError(MINIMAL_GUI_ERROR, __FILE__, __func__, __LINE__);
        throw Exception();
    }
}

void sp::ImageProc::getImagesMat(vector<Mat>& images, const SPConfig config) {
    char warningMSG[WARNING_MSG_LENGTH] = { '\0' };
    for (int i = 0; i < numOfImages; i++) {
        char imagePath[STRING_LENGTH + 1] = { '\0' };
        if (spConfigGetImagePath(imagePath, config, i) != SP_CONFIG_SUCCESS) {
            spLoggerPrintError(IMAGE_PATH_ERROR, __FILE__, __func__, __LINE__);
            throw Exception();
        }
        Mat img = imread(imagePath, IMREAD_GRAYSCALE);
        if (img.empty()) {
            sprintf(warningMSG, "%s %s", imagePath, IMAGE_NOT_EXIST_MSG);
            spLoggerPrintWarning(warningMSG, __FILE__, __func__, __LINE__);
            continue;
        }
        images.push_back(img);
    }
}

void sp::ImageProc::getFeatures(vector<Mat>& images, Mat& features) {
    //To store the keypoints that will be extracted by SIFT
    vector<KeyPoint> keypoints;
    //To store the SIFT descriptor of current image
    Mat descriptor;
    //To store all the descriptors that are extracted from all the images.

    //The SIFT feature extractor and descriptor
    Ptr<xfeatures2d::SiftDescriptorExtractor> detector =
            xfeatures2d::SIFT::create(numOfFeatures);

    //feature descriptors and build the vocabulary
    for (int i = 0; i < static_cast<int>(images.size()); i++) {
        //detect feature points
        detector->detect(images[i], keypoints);
        //compute the descriptors for each keypoint
        detector->compute(images[i], keypoints, descriptor);
        //put the all feature descriptors in a single Mat object
        features.push_back(descriptor);
    }
}

void sp::ImageProc::preprocess(const SPConfig config) {
    try {
        vector<Mat> images;
        Mat features;
        char pcaPath[STRING_LENGTH + 1] = { '\0' };
        getImagesMat(images, config);
        getFeatures(images, features);
        pca = PCA(features, Mat(), CV_PCA_DATA_AS_ROW, pcaDim);
        if (spConfigGetPCAPath(pcaPath, config) != SP_CONFIG_SUCCESS) {
            spLoggerPrintError(PCA_FILE_NOT_RESOLVED, __FILE__, __func__,
            __LINE__);
            throw Exception();
        }
        FileStorage fs(pcaPath, FileStorage::WRITE);
        fs << PCA_EIGEN_VEC_STR << pca.eigenvectors;
        fs << PCA_EIGEN_VAL_STR << pca.eigenvalues;
        fs << PCA_MEAN_STR << pca.mean;
        fs.release();
    } catch (...) {
        spLoggerPrintError(GENERAL_ERROR_MSG, __FILE__, __func__, __LINE__);
        throw Exception();
    }
}

void sp::ImageProc::initPCAFromFile(const SPConfig config) {
    if (!config) {
        spLoggerPrintError(GENERAL_ERROR_MSG, __FILE__, __func__, __LINE__);
        throw Exception();
    }
    char pcaFilename[STRING_LENGTH + 1] = { '\0' };
    if (spConfigGetPCAPath(pcaFilename, config) != SP_CONFIG_SUCCESS) {
        spLoggerPrintError(PCA_FILE_NOT_RESOLVED, __FILE__, __func__, __LINE__);
        throw Exception();
    }
    FileStorage fs(pcaFilename, FileStorage::READ);
    if (!fs.isOpened()) {
        spLoggerPrintError(PCA_FILE_NOT_EXIST, __FILE__, __func__, __LINE__);
        throw Exception();
    }
    fs[PCA_EIGEN_VEC_STR] >> pca.eigenvectors;
    fs[PCA_EIGEN_VAL_STR] >> pca.eigenvalues;
    fs[PCA_MEAN_STR] >> pca.mean;
    fs.release();
}

sp::ImageProc::ImageProc(const SPConfig config) {
    try {
        if (!config) {
            spLoggerPrintError(INVALID_ARG_ERROR, __FILE__, __func__, __LINE__);
            throw Exception();
        }
        SP_CONFIG_MSG msg;
        bool preprocMode = false;
        initFromConfig(config);
        if ((preprocMode = spConfigIsExtractionMode(config, &msg))) {
            preprocess(config);
        } else {
            initPCAFromFile(config);
        }
    } catch (...) {
        spLoggerPrintError(GENERAL_ERROR_MSG, __FILE__, __func__, __LINE__);
        throw Exception();
    }
}

SPPoint* sp::ImageProc::getImageFeatures(const char* imagePath, int index,
        int* numOfFeats) {
    vector<KeyPoint> keypoints;
    Mat descriptor, img, points;
    double* pcaSift = NULL;
    char errorMSG[STRING_LENGTH * 2];
    Ptr<xfeatures2d::SiftDescriptorExtractor> detector;
    if (!imagePath || !numOfFeats) {
        spLoggerPrintError(INVALID_ARG_ERROR, __FILE__, __func__, __LINE__);
        return NULL;
    }
    img = imread(imagePath, IMREAD_GRAYSCALE);
    if (img.empty()) {
        sprintf(errorMSG, "%s %s", imagePath, IMAGE_NOT_EXIST_MSG);
        spLoggerPrintError(errorMSG, __FILE__, __func__, __LINE__);
        return NULL;
    }
    detector = xfeatures2d::SIFT::create(numOfFeatures);
    detector->detect(img, keypoints);
    detector->compute(img, keypoints, descriptor);
    points = pca.project(descriptor);
    pcaSift = (double*) malloc(sizeof(double) * pcaDim);
    if (!pcaSift) {
        spLoggerPrintError(ALLOC_ERROR_MSG, __FILE__, __func__, __LINE__);
        return NULL;
    }
    *numOfFeats = points.rows;
    SPPoint* resPoints = (SPPoint*) malloc(sizeof(*resPoints) * points.rows);
    if (!resPoints) {
        free(pcaSift);
        spLoggerPrintError(ALLOC_ERROR_MSG, __FILE__, __func__, __LINE__);
        return NULL;
    }
    for (int i = 0; i < points.rows; i++) {
        for (int j = 0; j < points.cols; j++) {
            pcaSift[j] = (double) points.at<float>(i, j);
        }
        resPoints[i] = spPointCreate(pcaSift, pcaDim, index);
    }
    free(pcaSift);
    return resPoints;
}

void sp::ImageProc::showImage(const char* imgPath) {
    if (minimalGui) {
        Mat img = imread(imgPath, cv::IMREAD_COLOR);
        if (img.empty()) {
            spLoggerPrintWarning(IMAGE_NOT_EXIST_MSG, __FILE__, __func__,
            __LINE__);
            return;
        }
        imshow(windowName, img);
        waitKey(0);
        destroyAllWindows();
    } else {
        spLoggerPrintWarning(MINIMAL_GUI_NOT_SET_WARNING, __FILE__, __func__,
        __LINE__);
    }

}


SPImageProc.h

#ifndef SPIMAGEPROC_H_
#define SPIMAGEPROC_H_
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <vector>

extern "C" {
#include "SPConfig.h"
#include "SPPoint.h"
}

namespace sp {

/**
 * A class which supports different image processing functionalites.
 */
class ImageProc {
private:
    const char* windowName = "Software Project CBIR";
    int pcaDim;
    int numOfImages;
    int numOfFeatures;
    cv::PCA pca;
    bool minimalGui;
    void initFromConfig(const SPConfig);
    void getImagesMat(std::vector<cv::Mat>&, const SPConfig);
    void getFeatures(std::vector<cv::Mat>&,
            cv::Mat&);
    void preprocess(const SPConfig config);
    void initPCAFromFile(const SPConfig config);
public:

    /**
     * Creates a new object for the purpose of image processing based
     * on the configuration file.
     * @param config - the configuration file from which the object is created
     */
    ImageProc(const SPConfig config);

    /**
     * Returns an array of features for the image imagePath. All SPPoint elements
     * will have the index given by index. The actual number of features extracted
     * for this image will be stored in the pointer given by numOfFeats.
     *
     * @param imagePath - the target imagePath
     * @param index - the index  of the image in the database
     * @param numOfFeats - a pointer in which the actual number of feats extracted
     *                     will be stored
     * @return
     * An array of the actual features extracted. NULL is returned in case of
     * an error.
     */
    SPPoint* getImageFeatures(const char* imagePath,int index,int* numOfFeats);

    /**
     *  Displays the image given by imagePath. Notice that this function works
     *  only in MinimalGUI mode (otherwise a warnning message is printed).
     *
     *  @param imagePath - the path of the image to be displayed
     */
    void showImage(const char* imagePath);
};

}
#endif

Moshe
  • 136
  • 9
  • 1
    Which header did you edit? Surely not `SPImageProc.h`, with its prominent "DO NOT EDIT THIS FILE" comment right at the top? – John Bollinger May 10 '19 at 20:57
  • oh.. I edited SPImageProc.h . So how can I change that header ? I have to change it, because the reason I'm using jni is because I have a a cpp file (SPImageProc.cpp) and correspond header (SPImageProc.h) that have the Computer Vision function ready and tested , and I want to use them. – Moshe May 10 '19 at 21:22
  • You *do not* have to modify that header from what `javah` generates from the current version of the class file. There is absolutely no need whatever. Period. – John Bollinger May 10 '19 at 21:23
  • Please note that **javah** is obsolete and [not part of the up-to-date JDK](https://bugs.openjdk.java.net/browse/JDK-8166218). Java10 uses **javac -h** instead. – Alex Cohn May 11 '19 at 05:52

1 Answers1

0

You mention changing "the" header file, and indeed you present only one and your makefile references only that one. That header is machine-generated from your class file, and should not be edited, as the prominent comment at the top indicates. It will need to be regenerated if you modify the class in a way that affects the native interface -- that is, if you add or remove a native method or modify the signature of any of the existing ones.

That is why the Makefile you copied is set up to automatically rebuild the header if the Java class file changes. Of course, it also automatically rebuilds the class file if the Java source changes, as I presume will be less unexpected. So,

How to build project with jni without the building process recreating the header?

The header will not be rebuilt if you do not modify the Java source or for some other reason rebuild the Java class. You could also remove the makefile rule that causes it to be rebuilt when the Java parts do change, but then you'll just have to maintain it manually, and if you do that by running javah -- by far the easiest and safest alternative -- then you are back where you started, but with less automation.

Moreover, there is no reason to modify that header. It already contains everything that is needed by the declarations within, so nothing you could add benefits it, itself. Anything else you want to declare in your C sources can go directly into the sources themselves or into a separate header that you create and manage by hand.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Thanks a lot. Now I edited the post - I added at the end of the post, the original header and cpp file that I want to use. So I just copy everything that in them to my existed SPImageProc.cpp file? – Moshe May 10 '19 at 22:11
  • You can do that @Moshe, yes. You can also just rename one of the two headers (if you choose the JNI header, then you'll need also to change the makefile correspondingly). There is no requirement for any correspondence at all between header file names and source file names, and no limit in principle to how many headers a given source file may include. – John Bollinger May 10 '19 at 22:14
  • great! follow up question: now I tried to add the includes for openv, and in order to configure openv in eclipse, I need to configure it in the tab "tool settings". but this tab is missing. Someone asked about it (here: https://stackoverflow.com/questions/17687984/cant-find-the-tool-settings-in-eclipse-cdt) and the solution he got was to check "Generate Makefiles automatically ". But I'm afraid that it will make problems ...what do you think? – Moshe May 10 '19 at 22:41
  • I think that should really be a separate question, but I share your apprehension. – John Bollinger May 10 '19 at 23:46