Ask Your Question

Revision history [back]

Although I have not run timings, I suspect that going through formats such as XML or JSON would require more space and computing resources than using primitives.

Below is some code that can be used to save and retrieve MatOfKeyPoint to SQLite database in an Android environment. This is using OpenCV 2.4.11 so SIFT is available.

What you see when you run this application is your test image (which you need to supply and put in the drawable folder) with added keypoints.

The siftTest() method starts by computing keyPoints which is MatOfKeyPoint type. The code saves the underlying data of that object in the database, then reads that data out and creates a new object keyPointsFromDb, the contents of which are applied to the original image and the result is displayed.

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("native-lib");
        System.loadLibrary("opencv_java");
        System.loadLibrary("nonfree");

    }
    private ImageView imageView;
    private Bitmap inputImage; // make bitmap from image resource
    private FeatureDetector detector = FeatureDetector.create(FeatureDetector.SIFT);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        inputImage = BitmapFactory.decodeResource(getResources(), R.drawable.test);
        imageView = (ImageView) this.findViewById(R.id.imageView);
        siftTest();
    }

    public void siftTest() {
        Mat rgba = new Mat();
        Utils.bitmapToMat(inputImage, rgba);
        MatOfKeyPoint keyPoints = new MatOfKeyPoint();
        Imgproc.cvtColor(rgba, rgba, Imgproc.COLOR_RGBA2GRAY);
        detector.detect(rgba, keyPoints);

        // Save to database
        MatchingDatabaseAdapter.addKeypoints(keyPoints, getApplicationContext());

        // Opens database cursor
        MatchingDatabaseAdapter.getAllRecordsCursor(getApplicationContext()); 

        // Gets the first item in the database (as an example... you could loop through many/all)
        MatOfKeyPoint keyPointsFromDb = MatchingDatabaseAdapter.getKeypointsFromNextCursorPosition();

        // Closes database
        MatchingDatabaseAdapter.closeDb(); 

        Features2d.drawKeypoints(rgba, keyPointsFromDb, rgba);
        Utils.matToBitmap(rgba, inputImage);
        imageView.setImageBitmap(inputImage);

    }
}

Here is the database code which contains some details associated with converting to the byte array required for the database. I didn't include everything associated with using a database, since that's really a different topic.

public class MatchingDatabaseAdapter {
    ...
    ...
    ...

    public static void addKeypoints(MatOfKeyPoint keyPoints, Context context) {
        float[] data = new float[(int)keyPoints.total() * keyPoints.channels()]; // make a spot to save the data
        keyPoints.get(0,0,data); // load the data;
        ByteBuffer buffer = ByteBuffer.allocate(data.length * 4);
        for (int i = 0; i < data.length; i++){
            buffer.putFloat(data[i]);
        }
        byte[] byteArray = buffer.array();
        addBlob(byteArray, keyPoints.rows(), keyPoints.cols(), keyPoints.type(), context);
    }

    public static void addBlob(byte[] blob, int rows, int columns, int mattype, Context context) throws SQLException {
        if (mDb == null) mDb = openDb(context);
        ContentValues contentValues = new ContentValues();
        contentValues.put(DatabaseHelper.BLOB_FIELD_NAME, blob);
        contentValues.put(DatabaseHelper.ROWS_FIELD_NAME, rows);
        contentValues.put(DatabaseHelper.COLUMNS_FIELD_NAME, columns);
        contentValues.put(DatabaseHelper.MATTYPE_FIELD_NAME, mattype);
        long x = mDb.insert(DatabaseHelper.TABLE_NAME, null, contentValues);
        Log.v(TAG, "insert said " + x + " and blob was " + blob.length + " long.");
        closeDb();
    }

    public static Cursor getAllRecordsCursor(Context context) {
        if (mDb == null || !mDb.isOpen()) mDb = openDb(context);
        mCursor = mDb.query(DatabaseHelper.TABLE_NAME, null, null, null, null,null, null);
        boolean hasRecords = mCursor.moveToFirst();
        Log.v(TAG, "MatchingDatabaseAdapter.getAllRecordsCursor() cursor created. " + mCursor + " and " + (hasRecords?"has records":"has NO RECORDS"));
        return mCursor;
    }

     public static MatOfKeyPoint getKeypointsFromNextCursorPosition() {
        MatOfKeyPoint keyPoints = null;
        if (mCursor != null) {
            Log.v(TAG, "mCursor has " + mCursor.getCount());
            mCursor.moveToFirst();
            int rows = mCursor.getInt(DatabaseHelper.ROWS_FIELD_POSITION);
            int columns = mCursor.getInt(DatabaseHelper.COLUMNS_FIELD_POSITION);
            int mattype = mCursor.getInt(DatabaseHelper.MATTYPE_FIELD_POSITION);
            keyPoints = new MatOfKeyPoint();
            keyPoints.create(rows, columns, mattype);
            byte[] blob = mCursor.getBlob(DatabaseHelper.BLOB_FIELD_POSITION);
            ByteBuffer buffer = ByteBuffer.wrap(blob);
            FloatBuffer floatBuffer = buffer.asFloatBuffer();
            float[] floatArray = new float[floatBuffer.limit()];
            floatBuffer.get(floatArray);
            keyPoints.put(0, 0, floatArray);
        }
        return keyPoints;
    }

    // INNER CLASS DatabaseHelper
    static class DatabaseHelper extends SQLiteOpenHelper {
        private static DatabaseHelper mDatabaseHelper;
        private static final String DB_NAME = "blobDb";
        private static final String TABLE_NAME = "blobTable";
        private static final String ROWS_FIELD_NAME = "rowsField";
        public static final int ROWS_FIELD_POSITION = 1;
        private static final String COLUMNS_FIELD_NAME = "columnsField";
        public static final int COLUMNS_FIELD_POSITION = 2;
        private static final String MATTYPE_FIELD_NAME = "mattypeField";
        public static final int MATTYPE_FIELD_POSITION = 3;
        private static final String BLOB_FIELD_NAME = "blobField";
        private static final int BLOB_FIELD_POSITION = 4;

        private static final java.lang.String CREATE_TABLE_SQL =
                "CREATE TABLE " + TABLE_NAME + " ("
                        + "_id INTEGER PRIMARY KEY AUTOINCREMENT, "
                        + ROWS_FIELD_NAME + " INTEGER, "
                        + COLUMNS_FIELD_NAME + " INTEGER, "
                        + MATTYPE_FIELD_NAME + " INTEGER, "
                        + BLOB_FIELD_NAME + " BLOB"
                        + ");";

        private DatabaseHelper(Context context) {
            super(context, DB_NAME, null, DB_VERSION);
            mDatabaseHelper = this;
        }

        static synchronized DatabaseHelper getInstance(Context context) {
            if (mDatabaseHelper == null) {
                mDatabaseHelper = new DatabaseHelper(context.getApplicationContext());
            }
            return mDatabaseHelper;
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            Log.v(TAG, "Creating table: " + CREATE_TABLE_SQL);
            db.execSQL(CREATE_TABLE_SQL);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            Log.v(TAG, "onUpgrade() from " + oldVersion + " to " + newVersion);
            Log.v(TAG, "ALL DATA BEING REMOVED FROM THE DATABASE!!");
            db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME + ";");
            onCreate(db);
        }
    }
}

The full sample project runs and displays the surf key points, so if you want to save keypoints to a database, this code should get you where you want to go. Here is what's going on in that code, above:

MatOfKeyPoint to byte[] and some attributes

To save to the database, you need to save to a byte[] object. Using the MatOfKeyPoint.get() method, you can get a populated float[]. Then using ByteBuffer.putFloat(), you can loop through all of your floats, finally getting a populated byte[] by using ByteBuffer.array().

You also need to save some attirbutes MatOfKeyPoint.rows(), MatOfKeyPoint.cols(), and MatOfKeyPoint.type() to your database, along with the blob of byte[].

Database blob (byte[]) to MatOfKeyPoint

To reconstitute your MatOfKeyPoint object from the database, first you make a float[] out of your blob. Use ByteBuffer.wrap(blob), then run ByteBuffer.asFloatBuffer(), and finally FloatBuffer.get() with a properly sized new float[].

Now that you have yourFloatArray you run MatOfKeyPoint.create(rows, cols, type), those three things coming from the database record. Finally you run MatOfKeyPoint.put(0, 0, yourFloatArray).