C++ - Java で OpenCV の Keypoint の vector が受け渡しできない

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
まーさ
記事: 20
登録日時: 10年前

C++ - Java で OpenCV の Keypoint の vector が受け渡しできない

#1

投稿記事 by まーさ » 9年前

Java から C++ のOpenCVを JNI を経由して使っています。

C++側でロードした vector<vector <KeyPoint>> の参照を、Javaで保存し、必要に応じて C++ 側へ渡して vector<vector <KeyPoint>> から KeyPoint を取り出して処理をするプログラムをつくろうとしていますが、vector の参照を使ってうまくデータを再利用できなくて困っています。

※ KeyPoint は OpenCV の class です。
http://docs.opencv.org/modules/features ... t#KeyPoint

処理は以下のようにしています。

C++ で、vector<vector<KeyPoint>> をロードする関数を作成し、JNIインターフェースを作成します。
戻り値は、vector<vector<KeyPoint>> keypoints_listの参照です。

コード:

JNIEXPORT jlong JNICALL
Java_jp_jni_Load_loadKeypoints(JNIEnv *env, jobject jObj, jstring path) {
	TickMeter tm;
	tm.start();

	cout << "Keypoints Load start." << endl;
	vector<vector<KeyPoint>> keypoints_list;
	const char* keypoints_path_native = env->GetStringUTFChars(path, JNI_FALSE);
	string keypoints_path = string(keypoints_path_native);

	FileStorage fs_keypoints(keypoints_path, FileStorage::READ);
	FileNode n_keypoint = fs_keypoints.root();
	int class_cnt_keypoint = static_cast<int>(fs_keypoints["class_cnt"]);
	for (int i = 0; i < class_cnt_keypoint; i++) {
		vector < KeyPoint > v;
		FileNode node_keypoint_vec = fs_keypoints["keypoints" + itos(i)];
		read(node_keypoint_vec, v);
		keypoints_list.push_back(v);
	}
	fs_keypoints.release();

	tm.stop();
	double trainTime = tm.getTimeMilli();
	cout << "Keypoints Load end." << endl;
	cout << "load time: " << dtos(trainTime) << " (ms)" << endl;

	return (jlong)&keypoints_list[0];
}
Javaから、以下のように利用して、戻り値の long を保存しています。

コード:

public native long loadKeypoints(String dicPath);
上記で受け取った long を、別の処理で引数 jlong native_keypoints として渡して、vector<vector<KeyPoint>> を使用しようとしています。

コード:

JNIEXPORT jstring JNICALL
Java_jp_jni_Detect_detect (JNIEnv *env, jobject jObj, jlong native_keypoints){
  vector<vector<KeyPoint>>& keypoints_list = (vector<vector<KeyPoint>>&)native_keypoints;

  int keypoints_list_size = keypoints_list.size();
  cout << "keypoints cnt: " << keypoints_list_size << endl;
  vector<KeyPoint> keypoints = keypoints_list[0];  ←★エラー発生箇所
  …
}
実行すると、上記の「★エラー発生箇所」で以下のエラーが発生して困っています。

コード:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007fbc5ed5625e, pid=6487, tid=140447579678464
#
# JRE version: 7.0-b147
# Java VM: Java HotSpot(TM) 64-Bit Server VM (21.0-b17 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# C  [libLoadDictionary.so+0x2725e]  std::vector<cv::KeyPoint, std::allocator<cv::KeyPoint> >::size() const+0xc
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /root/git/jni/hs_err_pid6487.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.sun.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#
Java 、C++ 間で vector を使いまわしたい場合の処理は、上記のような考え方で良いのでしょうか?
また、エラーの原因をアドバイスいただけますと嬉しいです。

よろしくお願いします。

アバター
h2so5
副管理人
記事: 2212
登録日時: 13年前
住所: 東京
連絡を取る:

Re: C++ - Java で OpenCV の Keypoint の vector が受け渡しできない

#2

投稿記事 by h2so5 » 9年前

&keypoints_list[0]の型は vector<KeyPoint>* ですから、これを vector<vector<KeyPoint>>& にキャストすることはできません。
また、keypoints_listはローカル変数なので関数が終了した時点でアドレスは無効になります。

sleep

Re: C++ - Java で OpenCV の Keypoint の vector が受け渡しできない

#3

投稿記事 by sleep » 9年前

まーさ さんが書きました: Java 、C++ 間で vector を使いまわしたい場合の処理は、上記のような考え方で良いのでしょうか?
以下のサイトを参考にしてみてください。
http://stackoverflow.com/questions/1081 ... s-over-jni

まーさ
記事: 20
登録日時: 10年前

Re: C++ - Java で OpenCV の Keypoint の vector が受け渡しできない

#4

投稿記事 by まーさ » 9年前

h2so5さん

> また、keypoints_listはローカル変数なので関数が終了した時点でアドレスは無効になります。
> 以下のサイトを参考にしてみてください。
> http://stackoverflow.com/questions/1081 ... s-over-jni

ありがとうございます、勉強になります。
試してみます。結果がでたら書き込みいたします。

まーさ
記事: 20
登録日時: 10年前

Re: C++ - Java で OpenCV の Keypoint の vector が受け渡しできない

#5

投稿記事 by まーさ » 9年前

h2so5さん

重ねて質問なのですが、以下のようにDescriptorMatcher class(OpenCVのクラスです)を利用した場合はポインタの保存と再利用が可能です。

コード:

Java_jp_sprix_jni_recognize_LoadDictionary_load(JNIEnv *env, jobject jobj, jstring path) {
	const char* path_c = env->GetStringUTFChars(path, JNI_FALSE);
	string output_file = string(path_c);

	Ptr<DescriptorMatcher> lsh_load_desc_matcher;
	long vote_box_size = lsh_matcher_load(lsh_load_desc_matcher, output_file); ←この関数は別途下に記載

        lsh_load_desc_matcher.addref();
        return (long)(DescriptorMatcher*) lsh_load_desc_matcher;
}

コード:

void lsh_matcher_load(Ptr<DescriptorMatcher>& matcher, string& dictionary) {
	Ptr < flann::IndexParams > lshIndex = new flann::LshIndexParams(
			LSH_TABLE_SIZE, LSH_VECTOR_SIZE, NEAREST_K);
	lshIndex->setAlgorithm(cvflann::FLANN_INDEX_LSH);
	lshIndex->setString("dictionary", dictionary + "_index.xml");
	matcher = new FlannBasedMatcher(lshIndex);

	FileStorage fs_descriptor(dictionary + "_descriptors.xml",
			FileStorage::READ);

	FileNode n = fs_descriptor.root();
	long class_cnt = (long) static_cast<double>(fs_descriptor["class_cnt"]);
	for (long i = 0; i < class_cnt; i++) {
		vector < Mat > mats;
		Mat v;
		fs_descriptor["image" + ltos(i)] >> v;
		mats.push_back(v);
		matcher->add(mats);
	}
	fs_descriptor.release();
	matcher->train();
}
FlannBasedMatcher class を new すると、グローバルに変数が保持されるので可能なのでしょうか?

添付は、OpenCV の FlannBasedMatcher です。
添付ファイル
matchers.cpp
FlannBasedMatcher の定義のあるソースコード
(38.1 KiB) ダウンロード数: 121 回

アバター
h2so5
副管理人
記事: 2212
登録日時: 13年前
住所: 東京
連絡を取る:

Re: C++ - Java で OpenCV の Keypoint の vector が受け渡しできない

#6

投稿記事 by h2so5 » 9年前

newした場合ヒープ上にインスタンスが確保されるのでスコープを抜けてもアクセスできますが、使い終わった時にdeleteしないとメモリリークが発生します。
C++はJavaと違ってガベージコレクションがないので、オブジェクトの寿命をプログラマが管理する必要があります。

まーさ
記事: 20
登録日時: 10年前

Re: C++ - Java で OpenCV の Keypoint の vector が受け渡しできない

#7

投稿記事 by まーさ » 9年前

h2so5さん

ありがとうございます。
今回の処理の目的は、C++でただ1度ロードする vector<vector<KeyPoint>> を Java から任意のタイミングで使用することです。

ですので、教えていただいたURLの方法と、class を new する方法を試しています。
後者の方で、動作せずに困っています。以下のようにコーディングしました。

class RecognizeKeypoint を定義して、メンバ変数にvector<vector<KeyPoint>>をもつようにしました。

RecognizeKeypoint.cpp

コード:

#include "RecognizeKeypoint.hpp"

void RecognizeKeypoint::setlist(std::vector<std::vector<cv::KeyPoint>> list){
	recognize_keypoints_list = list;
}
RecognizeKeypoint.hpp

コード:

#include <iostream>
#include <string>
#include <cv.h>

#ifndef RECOGNIZEKEYPOINT_H_
#define RECOGNIZEKEYPOINT_H_

class RecognizeKeypoint{
public:
	std::vector<std::vector<cv::KeyPoint>> recognize_keypoints_list;
	void setlist(std::vector<std::vector<cv::KeyPoint>> list);
};

#endif
vector<vector<KeyPoint>> の生成方法は同じで、最後にクラスに値を設定しました。

コード:

JNIEXPORT jlong JNICALL
Java_jp_jni_Load_loadKeypoints(JNIEnv *env, jobject jObj, jstring path) {
    TickMeter tm;
    tm.start();
 
    cout << "Keypoints Load start." << endl;
    vector<vector<KeyPoint>> keypoints_list;
    const char* keypoints_path_native = env->GetStringUTFChars(path, JNI_FALSE);
    string keypoints_path = string(keypoints_path_native);
 
    FileStorage fs_keypoints(keypoints_path, FileStorage::READ);
    FileNode n_keypoint = fs_keypoints.root();
    int class_cnt_keypoint = static_cast<int>(fs_keypoints["class_cnt"]);
    for (int i = 0; i < class_cnt_keypoint; i++) {
        vector < KeyPoint > v;
        FileNode node_keypoint_vec = fs_keypoints["keypoints" + itos(i)];
        read(node_keypoint_vec, v);
        keypoints_list.push_back(v);
    }
    fs_keypoints.release();
 
    tm.stop();
    double trainTime = tm.getTimeMilli();
    cout << "Keypoints Load end." << endl;
    cout << "load time: " << dtos(trainTime) << " (ms)" << endl;
 
    /* ここから追加したコード */

    Ptr<RecognizeKeypoint> native_keypoints = new RecognizeKeypoint();
    native_keypoints->setlist(keypoints);

    return (long)(RecognizeKeypoint*)native_keypoints;
}
上記の戻り値 native_keypoints をアドレスとしたポインタを使って、vector<vector<KeyPoint>> にアクセスしようとしています。

コード:

try{
    RecognizeKeypoint* rk = (RecognizeKeypoint*)native_keypoints;
    vector<vector<KeyPoint>> keypoints = rk->recognize_keypoints_list;
    int keypoints_size = keypoints.size();
    cout << "keypoints cnt: " << keypoints_size << endl;
    vector<KeyPoint> keypoint = keypoints[0]; // ←★ここで bad alloc
}catch(bad_alloc& ba){
    cerr << "bad_alloc caught: " << ba.what() << endl;
}
「←★ここで bad alloc」の行で bad_alloc が発生します。また、そもそも vector<vector<KeyPoint>>のsizeが0でした。

classをnewしても、メンバ変数はヒープ領域に残らず解放されてしまうのでしょうか?

アバター
h2so5
副管理人
記事: 2212
登録日時: 13年前
住所: 東京
連絡を取る:

Re: C++ - Java で OpenCV の Keypoint の vector が受け渡しできない

#8

投稿記事 by h2so5 » 9年前

native_keypointsの型は Ptr<RecognizeKeypoint> ですから、RecognizeKeypoint* にキャストすることはできません。
しかもPtr<RecognizeKeypoint>はスマートポインタなのでローカル変数と同じように自動で解放されてしまいます。

スマートポインタを使用せずに、
RecognizeKeypoint* native_keypoints = new RecognizeKeypoint();
とすれば動作すると思います。

まーさ
記事: 20
登録日時: 10年前

Re: C++ - Java で OpenCV の Keypoint の vector が受け渡しできない

#9

投稿記事 by まーさ » 9年前

h2so5さん

ありがとうございます。

> スマートポインタを使用せずに、
> RecognizeKeypoint* native_keypoints = new RecognizeKeypoint();
> とすれば動作すると思います。

教えていただいた方法で動作しました。とても助かりました。

うまく動いていたコードもスマートポインタを使っています。
http://dixq.net/forum/viewtopic.php?f=3&t=15646#p124449

スマートポインタを使用したとき、参照カウンタを利用することで、使っていない場合と同じように動作させることが可能なのでしょうか?

まーさ
記事: 20
登録日時: 10年前

Re: C++ - Java で OpenCV の Keypoint の vector が受け渡しできない

#10

投稿記事 by まーさ » 9年前

試してみました。

OpenCVのスマートポインタ Ptr のカウントアップ addref を使用することで、解放を防ぐことができました。

まとめますと、対処方法は以下のようにスマートポインタを使わないか、

コード:

RecognizeKeypoint* rk = new RecognizeKeypoint();
または、以下のようにスマートポインタの参照カウンタをあげておくことでいけました。

コード:

Ptr<RecognizeKeypint> rk = new RecognizeKeypoint();
rk.addref();
h2so5さん、アドバイスいただきありがとうございました。

閉鎖

“C言語何でも質問掲示板” へ戻る