Use kmeans to determine the prevailing colors in an image (android/java)

Hi!
I’m trying to use kmeans to determine the prevailing colors in an image.
The main idea: there is a list of URLs in the format “content://media/external/images/media/65”. I read the data from the local storage, transform it into a bitmap, send this bitmap for clustering, get the primary colors in the image and display them in the application.
I’ll show you the code of a minimal project in which I’m testing clustering.
I changed the opencv version, the criteria for kmeans - don’t work.
I tried to use kmeans in python, everything works there. Not in java.

compileSdk = 34.
opencv = “4.10.0”

Dependencies ok.

package com.fuxkinghatred.testopencv;

import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
import org.opencv.core.TermCriteria;
import org.opencv.imgproc.Imgproc;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        if (loadOpenCV()) return;

        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
                R.drawable.a3);
        if (bitmap == null) {
            Log.d(TAG, "onCreate: Failed to upload image");
            return;
        }

        Mat mat = new Mat();
        Utils.bitmapToMat(bitmap, mat);
        Log.d(TAG, "onCreate: mat: " + mat);

        List<Scalar> dominantColors = getDominantColors(mat, 3);
        for (Scalar color : dominantColors) {
            Log.d(TAG, "onCreate: dominantColors: color : " + color);
        }

        String colorString = "";
        for (Scalar color : dominantColors) {
            Log.d(TAG, "onCreate: dominantColors: color : " + color);
        }
        Log.d(TAG, "onCreate: colorString" + colorString);

    }

    private List<Scalar> getDominantColors(Mat image, int k) {
        if (image.channels() != 3) {
            Imgproc.cvtColor(image, image, Imgproc.COLOR_RGBA2RGB);
        }

        List<Mat> channels = new ArrayList<>();
        Core.split(image, channels);
        Mat rgb = new Mat();
        Core.merge(channels.subList(0, 3), rgb);

        Mat pixels = rgb.reshape(1, rgb.rows() * rgb.cols());
        Log.d(TAG, "Pixels dimensions after reshape: " + pixels.rows() + " x " + pixels.cols());
        pixels.convertTo(pixels, CvType.CV_32F);
        Core.normalize(pixels, pixels, 0, 1, Core.NORM_MINMAX);
        Log.d(TAG, "Normalized pixels data:");
        for (int i = 0; i < Math.min(10, pixels.rows()); i++) {
            Log.d(TAG, Arrays.toString(pixels.get(i, 0)));
        }

        Log.d(TAG, "Pixels dimensions: " + pixels.rows() + " x " + pixels.cols());
        Log.d(TAG, "Pixels type: " + pixels.type());

        Mat labels = new Mat();
        Mat centers = new Mat();
        TermCriteria criteria = new TermCriteria(TermCriteria.EPS + TermCriteria.COUNT, 200, 0.001);

        int attempts = 20;
        Log.d(TAG, "getDominantColors: criteria: " + criteria);

        Log.d(TAG, "pixels data before kmeans:");
        for (int i = 0; i < Math.min(10, pixels.rows()); i++) { 
            Log.d(TAG, Arrays.toString(pixels.get(i, 0)));
        }

        Log.d(TAG, "Before kmeans: centers.rows(): " + centers.rows() + ", centers.cols(): " + centers.cols());
        try {
            Core.kmeans(pixels, k, labels, criteria, attempts, Core.KMEANS_PP_CENTERS);
        } catch (Exception e) {
            Log.e(TAG, "Core.kmeans failed: " + e.getMessage(), e);
                return new ArrayList<>();
        }

        List<Scalar> dominantColors = new ArrayList<>();
        for (int i = 0; i < centers.rows(); i++) {
            double[] data = centers.get(i, 0);
            dominantColors.add(new Scalar(data[0], data[1], data[2]));
            Log.d(TAG, "getDominantColors: dominantColors.get(" + i + "): " + dominantColors.get(i));
        }
        Log.d(TAG, "After kmeans: centers.rows(): " + centers.rows() + ", centers.cols(): " + centers.cols());
        Log.d(TAG, "After kmeans: dominantColors.size(): " + dominantColors.size());
        return dominantColors;
    }

    private boolean loadOpenCV() {
        if (OpenCVLoader.initLocal())
            Log.d(TAG, "OpenCV loaded successfully");
        else {
            Log.e(TAG, "OpenCV initialization failed!");
            Toast.makeText(this, "OpenCV initialization failed!", Toast.LENGTH_LONG).show();
            return true;
        }
        return false;
    }
}

Image;

Logcat:

20:04:08.363 MainActivity             D  OpenCV loaded successfully
20:04:08.481 MainActivity             D  onCreate: mat: Mat [ 1280*1920*CV_8UC4, isCont=true, isSubmat=false, nativeObj=0xe3c878a0, dataAddr=0xb7980000 ]
20:04:08.558 MainActivity             D  Pixels dimensions after reshape: 2457600 x 3
20:04:08.625 MainActivity             D  Normalized pixels data:
20:04:08.626 MainActivity             D  [0.4392157196998596]
20:04:08.628 MainActivity             D  [0.4392157196998596]
20:04:08.628 MainActivity             D  [0.44705885648727417]
20:04:08.629 MainActivity             D  [0.4549019932746887]
20:04:08.630 MainActivity             D  [0.46666669845581055]
20:04:08.630 MainActivity             D  [0.4784314036369324]
20:04:08.631 MainActivity             D  [0.4941176772117615]
20:04:08.632 MainActivity             D  [0.5098039507865906]
20:04:08.632 MainActivity             D  [0.5254902243614197]
20:04:08.633 MainActivity             D  Pixels dimensions: 2457600 x 3
20:04:08.633 MainActivity             D  Pixels type: 5
20:04:08.634 MainActivity             D  getDominantColors: criteria: { type: 2, maxCount: 500, epsilon: 1.0E-4}
20:04:08.635 MainActivity             D  pixels data before kmeans:
20:04:08.635 MainActivity             D  [0.4392157196998596]
20:04:08.636 MainActivity             D  [0.4392157196998596]
20:04:08.637 MainActivity             D  [0.4392157196998596]
20:04:08.637 MainActivity             D  [0.44705885648727417]
20:04:08.638 MainActivity             D  [0.4549019932746887]
20:04:08.639 MainActivity             D  [0.46666669845581055]
20:04:08.639 MainActivity             D  [0.4784314036369324]
20:04:08.640 MainActivity             D  [0.4941176772117615]
20:04:08.641 MainActivity             D  [0.5098039507865906]
20:04:08.641 MainActivity             D  [0.5254902243614197]
20:04:08.642 MainActivity             D  Before kmeans: centers.rows(): 0, centers.cols(): 0
20:05:07.164 MainActivity             D  After kmeans: centers.rows(): 0, centers.cols(): 0
20:05:07.164 MainActivity             D  After kmeans: dominantColors.size(): 0
20:05:07.164 MainActivity             D  onCreate: colorString