Search code examples
javaopencvmatandroid

Save Mat (openCV) to SharedPreferences Android


I'm writting app, which using KNearest. I wrote code to train model, but every restart app, I must train data again, so I would like to save train data to SharedPreferences once and using it after.

I know that I must convert Mat to byte[] and then to String, but decode is not working, I got error:

(layout == ROW_SAMPLE && responses.rows == nsamples) 
|| (layout == COL_SAMPLE && responses.cols == nsamples) 
in function void cv::ml::TrainDataImpl::setData(cv::InputArray, 
int, cv::InputArray, cv::InputArray, 
cv::InputArray, cv::InputArray, cv::InputArray, cv::InputArray)

Code:

protected Void doInBackground(Void... args) {
        // Constants.TRAIN_SAMPLES = 10

        Mat trainData = new Mat(0, 200 * 200, CvType.CV_32FC1); // 0 x 40 000
        Mat trainClasses = new Mat(Constants.TRAIN_SAMPLES, 1, CvType.CV_32FC1); // 10 x 1
        float[] myint = new float[Constants.TRAIN_SAMPLES + 1];
        for (i = 1; i <= Constants.TRAIN_SAMPLES; i++)
            myint[i] = (float) i;

        trainClasses.put(0, 0, myint);
        KNearest knn = KNearest.create();

        String val = " ";
        val = sharedPref.getString("key", " ");

        // empty SharedPreferences
        if (val.equals(" ")) {

            // get all images from external storage
            for (i = 1; i <= Constants.TRAIN_SAMPLES; i++) {

                String photoPath = Environment.getExternalStorageDirectory().toString() + "/ramki/ramka_" + i + ".png";
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inPreferredConfig = Bitmap.Config.ARGB_8888;
                Bitmap bitmap = BitmapFactory.decodeFile(photoPath, options);

                Utils.bitmapToMat(bitmap, img);

                if (img.channels() == 3) {
                    Imgproc.cvtColor(img, img, Imgproc.COLOR_RGB2GRAY);
                } else if (img.channels() == 4) {
                    Imgproc.cvtColor(img, img, Imgproc.COLOR_RGBA2GRAY);
                }

                Imgproc.resize(img, img, new Size(200, 200));
                img.convertTo(img, CvType.CV_32FC1);
                img = img.reshape(1, 1); //  1 x 40 000 ( 200x200 )

                trainData.push_back(img);
                publishProgress(i);
            }


            trainData.convertTo(trainData, CvType.CV_8U);
            // save this trainData (Mat) to SharedPreferences
            saveMatToPref(trainData);

        } else {
            // get trainData from SharedPreferences
            val = sharedPref.getString("key", " ");

            byte[] data = Base64.decode(val, Base64.DEFAULT);

            trainData.convertTo(trainData, CvType.CV_8U);
            trainData.put(0, 0, data);
        }

        trainData.convertTo(trainData, CvType.CV_32FC1);
        knn.train(trainData, Ml.ROW_SAMPLE, trainClasses);

        trainClasses.release();
        trainData.release();
        img.release();

        onPostExecute();
        return null;
    }


 public void saveMatToPref(Mat mat) {

    if (mat.isContinuous()) {
        int cols = mat.cols();
        int rows = mat.rows();
        byte[] data = new byte[cols * rows];

        // there, data contains {0,0,0,0,0,0 ..... } 400 000 items
        mat.get(0, 0, data);

        String dataString = new String(Base64.encode(data, Base64.DEFAULT));

        SharedPreferences.Editor mEdit1 = sharedPref.edit();
        mEdit1.putString("key", dataString);

        mEdit1.commit();

    } else {
        Log.i(TAG, "Mat not continuous.");
    }
 }

When I decode, my trainData look like this:

Mat [ 0*40000*CV_32FC1 ..]

but should: Mat [ 10*40000*CV_32FC1 ..]

Can anybody help me to encode and decode Mat? Thx for help.


Solution

  • As @Miki mention, problem was in types. Now it works, but only with Mat size around 200 x 40 000 in my case, if it's bigger, I have outOfMemory excepion...

                String val = " ";
            val = sharedPref.getString("key", " ");
    
            // empty SharedPreferences
            if ( ! val.equals(" ")) {
    
                // get all images from external storage
                for (i = 1; i <= Constants.TRAIN_SAMPLES; i++) {
    
                    String photoPath = Environment.getExternalStorageDirectory().toString() + "/ramki/ramka_" + i + ".png";
                    BitmapFactory.Options options = new BitmapFactory.Options();
                    options.inPreferredConfig = Bitmap.Config.ARGB_8888;
                    Bitmap bitmap = BitmapFactory.decodeFile(photoPath, options);
    
                    Utils.bitmapToMat(bitmap, img);
    
                    if (img.channels() == 3) {
                        Imgproc.cvtColor(img, img, Imgproc.COLOR_RGB2GRAY);
                    } else if (img.channels() == 4) {
                        Imgproc.cvtColor(img, img, Imgproc.COLOR_RGBA2GRAY);
                    }
    
                    Imgproc.resize(img, img, new Size(200, 200));
                    img.convertTo(img, CvType.CV_32FC1);
                    img = img.reshape(1, 1); //  1 x 40 000 ( 200x200 )
    
                    trainData.push_back(img);
                    publishProgress(i);
                }
    
                // save this trainData (Mat) to SharedPreferences
                saveMatToPref(trainData);
    
            } else {
                // get trainData from SharedPreferences
                val = sharedPref.getString("key", " ");
    
                byte[] data = Base64.decode(val, Base64.DEFAULT);
    
                trainData = new Mat(Constants.TRAIN_SAMPLES, 200 * 200, CvType.CV_32FC1); 
    
                float[] f = toFloatArray(data);
    
                trainData.put(0, 0, f);
            }
    
            knn.train(trainData, Ml.ROW_SAMPLE, trainClasses);
    
    
    
      public void saveMatToPref(Mat mat) {
    
        if (mat.isContinuous()) {
    
            int size = (int)( mat.total() * mat.channels() );
    
            float[] data = new float[ size ];
    
            byte[] b = new byte[ size ];
    
            mat.get(0, 0, data);
    
            b = FloatArray2ByteArray(data);
    
            String dataString = new String(Base64.encode(b, Base64.DEFAULT));
    
            SharedPreferences.Editor mEdit1 = sharedPref.edit();
            mEdit1.putString("key", dataString);
    
            mEdit1.commit();
    
        } else {
            Log.i(TAG, "Mat not continuous.");
        }
    }
    
    private static float[] toFloatArray(byte[] bytes) {
        ByteBuffer buffer = ByteBuffer.wrap(bytes);
        FloatBuffer fb = buffer.asFloatBuffer();
        float[] floatArray = new float[fb.limit()];
        fb.get(floatArray);
        return floatArray;
    }
    
    public static byte[] FloatArray2ByteArray(float[] values){
        ByteBuffer buffer = ByteBuffer.allocate(4 * values.length);
    
        for (float value : values)
            buffer.putFloat(value);
    
        return buffer.array();
    }
    

    If someone have better solution, please add.