0

I am working on an android project to process images using Opencv. I wrote an android jni function that should return a vector but I can't figure out how to do that right.

I tried to convert the vector to jobjectArray but it's not working. Here is the code I'm working on:

    jobjectArray
    Java_com_grimg_testtt_MainActivity_getQuadrilateral(
    JNIEnv *env,
    jobject /* this */,
    cv::Mat & grayscale,
    cv::Mat & output) {
std::vector<std::string> vec;
cv::Mat approxPoly_mask(grayscale.rows, grayscale.cols, CV_8UC1);
approxPoly_mask = cv::Scalar(0);
std::vector<std::vector<cv::Point>> contours;
std::vector<int> indices(contours.size());
std::iota(indices.begin(), indices.end(), 0);
sort(indices.begin(), indices.end(), [&contours](int lhs, int rhs) {
    return contours[lhs].size() > contours[rhs].size();
});
/// Find the convex hull object for each contour
std::vector<std::vector<cv::Point>> hull(1);
cv::convexHull(cv::Mat(contours[indices[0]]), hull[0], false);

std::vector<std::vector<cv::Point>> polygon(1);
approxPolyDP(hull[0], polygon[0], 20, true);
drawContours(approxPoly_mask, polygon, 0, cv::Scalar(255));
//imshow("approxPoly_mask", approxPoly_mask);

if (polygon[0].size() >= 4) // we found the 4 corners
{
    return(polygon[0]);
}

return(std::vector<cv::Point>());

}

In the last two lines I'm getting this error which is obvious:

    Returning 'std::vector<cv::Point> &' from a function returning 'jobjectArray': Types 'jobjectArray' and 'std::vector<cv::Point>' are not compatible.

What can I do to get over this problem?

Edit :

jclass clazz = (*env).FindClass("java/util/ArrayList");
jobjectArray result = (*env).NewObjectArray(polygon[0].size(), clazz, 0);

if (polygon[0].size() >= 4) // we found the 4 corners
{
    for (int n=0;n<polygon[0].size();n++)
    {
        cv::Point point = (cv::Point) static_cast<cv::Point>(polygon[0][n]);
        (*env).CallVoidMethod(result, (*env).GetMethodID(clazz, "add", "(java/lang/Object)V"), point);

    }
    return result;
}

return result;

}

Edit 2 :

    jclass ptCls = env->FindClass("java/awt/Point");
jobjectArray result = (*env).NewObjectArray(polygon[0].size(), ptCls, NULL);

if (result == NULL) return NULL;

if (polygon[0].size() >= 4) // we found the 4 corners
{
    for (int n=0;n<polygon[0].size();n++)
    {
        jobject point = (jobject) static_cast<jobject>(polygon[0][n]);
        //(*env).CallVoidMethod(result, (*env).GetMethodID(ptCls, "add", "(java/lang/Object)V"), polygon[0][n]);
        (*env).SetObjectArrayElement(result, polygon[0].size(), point);

    }
    return result;
}

return result;

Error

    error: cannot cast from type 'std::__ndk1::__vector_base<cv::Point_<int>, std::__ndk1::allocator<cv::Point_<int> > >::value_type' (aka 'cv::Point_<int>') to pointer type 'jobject' (aka '_jobject *')

        jobject point = (jobject) static_cast<jobject>(polygon[0][n]);
Community
  • 1
  • 1
Amine
  • 2,241
  • 2
  • 19
  • 41
  • Post the code where you are trying to do the conversion. At the moment you're just asking for us to write that code for you. You need to show what you have tried. – john Jan 28 '19 at 09:29
  • Thank you Sir for your response, I tried many solutions but nothing works so I deleted that code, but I know I should create a wrapper for Vector in C++ and I wish you can help me with that. – Amine Jan 28 '19 at 09:37
  • Just show your best effort. You are more likely to get an answer if you show what you have tried. – john Jan 28 '19 at 09:39
  • I guess function return type is `std::vector` ans you are returning `std::vector&` – Mayur Jan 28 '19 at 09:46
  • Can you should function signature? – Mayur Jan 28 '19 at 09:47
  • @john thank you Sir, I will. – Amine Jan 28 '19 at 10:09
  • @Mayur The problem is that I am returning a vector and the jni function is expecting a jobjectArray. – Amine Jan 28 '19 at 10:10
  • @john Please take a look at the edit and tell me what's wrong. I tried to build and I get this error : error: cannot pass object of non-trivial type 'cv::Point' (aka 'Point_') through variadic method; call will abort at runtime – Amine Jan 28 '19 at 10:34
  • @Amine I'm not an expert but I believe the problem is that you have to convert your C++ cv:Point object to it's Java equivalent before you add it to your jobjectArray. What rustyx said in his second last paragraph in other words. – john Jan 28 '19 at 10:40

1 Answers1

2

In the JNI layer you should map native objects to Java objects (Java objects are allocated on the JVM heap).

cv::Point needs to be converted to a Java class and std::vector needs to be converted to jobjectArray.

Use (*env)->NewObjectArray to create jobjectArray like this:

jobjectArray result = (*env)->NewObjectArray(env, size, PointCls, NULL);
if (result == NULL) return NULL;

The PointCls should refer to the Java class that corresponds to your native Point class.

Then iterate over each native cv::Point object, create a Java Point from it, copy the fields, and put it into the array (using (*env)->SetObjectArrayElement).

Then you can return the result array.

For example like this:

std::vector<cv::Point> const& input = polygon[0];
jclass doubleArray = env->FindClass("[D");
if (doubleArray == NULL) return NULL;
jobjectArray result = env->NewObjectArray(input.size(), doubleArray, NULL);
if (result == NULL) return NULL;
for (int i = 0; i < input.size(); ++i) {
    jdoubleArray element = env->NewDoubleArray(2);
    if (element == NULL)
        break;
    jdouble buf[2] = { input[i].x, input[i].y };
    env->SetDoubleArrayRegion(element, 0, 2, buf);
    env->SetObjectArrayElement(result, i, element);
}
return result;

This will return a 2D array double[][], with x, y corresponding to 0, 1 in the second dimension.

rustyx
  • 80,671
  • 25
  • 200
  • 267
  • If no Java `Point` equivalent is handy, you can also just flatten the `cv::Point` vector into a JNI `double[]` of the shape `{x1, y1, x2, y2, ...}` – Botje Jan 28 '19 at 09:59
  • Thank you Sir for you answer, actually that's what I am trying to do. I will keep trying. – Amine Jan 28 '19 at 10:07
  • @rustyx I tried to convert your answer instructions into code but I'm getting an error, take a look at edit 2. – Amine Jan 28 '19 at 13:14
  • `cv::Point` is a native object, you can't add it to a Java array, for each point you still need to create a new Java object and manually copy the point values (x, y) to it. `java.awt.Point` has two int's, but you need two double's, so it won't work. Maybe create a 2-element double array for each point, and return a 2D array: `double[][]`. [Example](https://stackoverflow.com/questions/43865462/). – rustyx Jan 28 '19 at 13:29
  • I'm trying to create a jstring object from x and y values of the point and separate them with a ',' and then in java I extract each x and y and recreate the points. I'm trying this because I want to try the image processing method then I will optimize the code.How do you suggest converting the string to jstring ? – Amine Jan 28 '19 at 13:58
  • It's much easier to return a 2D array - see my edit. – rustyx Jan 28 '19 at 16:46
  • @rustyx Better approach. Thank you. – Amine Jan 29 '19 at 08:01