как оптимизировать матрицу свертки в андроиде

Я использую Convolution Matrix для своего приложения для Android для создания изображения Emboss. я определил класс для него как:

public class ConvolutionMatrix {
public static final int SIZE = 3;

public double[][] Matrix;
public double Factor = 1;
public double Offset = 1;

public ConvolutionMatrix(int size) {
    Matrix = new double[size][size];
}

public void setAll(double value) {
    for (int x = 0; x < SIZE; ++x) {
        for (int y = 0; y < SIZE; ++y) {
            Matrix[x][y] = value;
        }
    }
}

public void applyConfig(double[][] config) {
    for (int x = 0; x < SIZE; ++x) {
        for (int y = 0; y < SIZE; ++y) {
            Matrix[x][y] = config[x][y];
        }
    }
}

public static Bitmap computeConvolution3x3(Bitmap src,
        ConvolutionMatrix matrix) {
    int width = src.getWidth();
    int height = src.getHeight();
    Bitmap result = Bitmap.createBitmap(width, height, src.getConfig());

    int A, R, G, B;
    int sumR, sumG, sumB;
    int[][] pixels = new int[SIZE][SIZE];

    for (int y = 0; y < height - 2; ++y) {
        for (int x = 0; x < width - 2; ++x) {

            // get pixel matrix
            for (int i = 0; i < SIZE; ++i) {
                for (int j = 0; j < SIZE; ++j) {
                    pixels[i][j] = src.getPixel(x + i, y + j);
                }
            }

            // get alpha of center pixel
            A = Color.alpha(pixels[1][1]);

            // init color sum
            sumR = sumG = sumB = 0;

            // get sum of RGB on matrix
            for (int i = 0; i < SIZE; ++i) {
                for (int j = 0; j < SIZE; ++j) {
                    sumR += (Color.red(pixels[i][j]) * matrix.Matrix[i][j]);
                    sumG += (Color.green(pixels[i][j]) * matrix.Matrix[i][j]);
                    sumB += (Color.blue(pixels[i][j]) * matrix.Matrix[i][j]);
                }
            }

            // get final Red
            R = (int) (sumR / matrix.Factor + matrix.Offset);
            if (R < 0) {
                R = 0;
            } else if (R > 255) {
                R = 255;
            }

            // get final Green
            G = (int) (sumG / matrix.Factor + matrix.Offset);
            if (G < 0) {
                G = 0;
            } else if (G > 255) {
                G = 255;
            }

            // get final Blue
            B = (int) (sumB / matrix.Factor + matrix.Offset);
            if (B < 0) {
                B = 0;
            } else if (B > 255) {
                B = 255;
            }

            // apply new pixel
            result.setPixel(x + 1, y + 1, Color.argb(A, R, G, B));
        }
    }

    // final image
    return result;
}

}

Это дает мне правильный результат, но для вычисления результата требуется слишком много времени. Есть ли способ сделать расчет быстрее и работать эффективнее?


person Community    schedule 12.01.2013    source источник


Ответы (2)


Взгляните на: демонстрацию свертки. Это приложение, которое сравнивает реализацию свертки, выполненную на Java, и на C++. Излишне говорить, что вариант C++ работает более чем в 10 раз быстрее.

Поэтому, если вам нужна скорость, реализуйте ее через NDK или через шейдеры.

person abergmeier    schedule 13.01.2013
comment
Спасибо за ответ. Можете ли вы дать мне ссылку, связанную с примером шейдеров? - person ; 16.01.2013
comment
Google выдает это. - person abergmeier; 16.01.2013
comment
Пфф. Абсурд. Когда вы берете на себя легкую отговорку, вы всегда заканчиваете тем, что не ищете ответ. Конечно, бла немного медленнее. Насколько медленнее этот фактический код по сравнению с правильным кодом C? Большинство вещей для Android работает на чипах ARM и непосредственно на чипе, а не на виртуальной машине, поскольку он кодирует их напрямую. - person Tatarize; 18.02.2015

Суть вашего замедления в следующем:

    // apply new pixel
    result.setPixel(x + 1, y + 1, Color.argb(A, R, G, B));

Это разумный объем работы на каждой итерации для установки каждого пикселя пиксель за пикселем, они не бесплатны в классе растровых изображений. Гораздо лучше вызвать процедуру getPixels() и возиться с необработанными пикселями, а затем вернуть их обратно, только один раз, когда вы закончите.

Вы также можете жестко закодировать тиснение (большую часть времени вы собираете кучу данных и умножаете их на ноль с этим ядром, вы легко обманываете и захватываете, как три пикселя, которые вам нужны.

private static int hardEmboss(int[] pixels, int stride, int index, int[][] matrix, int parts) {
        //ignoring the matrix
        int p1 = pixels[index];
        int p2 = pixels[index + stride + 1];
        int p3 = pixels[index + stride + stride + 2];
        int r = 2 * ((p1 >> 16) & 0xFF) - ((p2 >> 16) & 0xFF) - ((p3 >> 16) & 0xFF);
        int g = 2 * ((p1 >> 8) & 0xFF) - ((p2 >> 8) & 0xFF) - ((p3 >> 8) & 0xFF);
        int b = 2 * ((p1) & 0xFF) - ((p2) & 0xFF) - ((p3) & 0xFF);
        return 0xFF000000 | ((crimp(r) << 16) | (crimp(g) << 8) | (crimp(b)));
    }

Предполагая, что ваше ядро ​​emboss:

   int[][] matrix = new int[][]{
            {2, 0, 0},
            {0, -1, 0},
            {0, 0, -1}
    };

Кроме того, мало кто знает, что в стандартном алгоритме свертки есть критический недостаток, из-за которого возврат пикселя результатов в центр является ошибочным. Если вы вернете его в верхний левый угол, вы сможете просто обрабатывать все данные в одном и том же отпечатке памяти слева направо и сверху вниз в операции сканирования.

    public static int crimp(int v) { return (v > 255)?255:((v < 0)?0:v); }
    public static void applyEmboss(int[] pixels, int stride) { 
        //stride should be equal to width here, and pixels.length == bitmap.height * bitmap.width;
        int pos;
        pos = 0;
        try {
            while (true) {
                int p1 = pixels[pos];
                int p2 = pixels[pos + stride + 1];
                int p3 = pixels[pos + stride + stride + 2];
                int r = 2 * ((p1 >> 16) & 0xFF) - ((p2 >> 16) & 0xFF) - ((p3 >> 16) & 0xFF);
                int g = 2 * ((p1 >> 8) & 0xFF) - ((p2 >> 8) & 0xFF) - ((p3 >> 8) & 0xFF);
                int b = 2 * ((p1) & 0xFF) - ((p2) & 0xFF) - ((p3) & 0xFF);
                pixels[pos++] = 0xFF000000 | ((crimp(r) << 16) | (crimp(g) << 8) | (crimp(b)));
            }
        }
        catch (ArrayIndexOutOfBoundsException e) { }
    }

Недостатком является то, что пиксель кажется сдвинутым влево и вверх на 1 пиксель. Хотя, если вы сделаете еще одну заливку строки сканирования назад, вы можете сдвинуть их назад. И вся фигня здесь закончится в виде 2-х рядов справа и снизу (некоторые из них будут заполнены рельефной ерундой, потому что у меня не было тормозов, чтобы проверить эти места). Это означает, что если вы хотите отрезать это при чтении, пиксели уменьшите высоту и ширину на 2 и оставьте шаг равным исходной ширине. Поскольку все хорошие данные будут в верхнем бите, вам вообще не нужно возиться со смещением.

Кроме того, просто используйте рендерскрипт.

person Tatarize    schedule 18.02.2015