Результат выделения RenderScript возвращает черное растровое изображение

#image-processing #allocation #android-bitmap #renderscript #android-renderscript

#обработка изображений #выделение #android-bitmap #renderscript #android-renderscript

Вопрос:

несколько дней назад я только начал изучать RenderScript. Мне удалось создать несколько простых фильтров для обработки изображений, например, оттенки серого, изменение цвета. Сейчас я работаю над фильтрами Canny edge, но безуспешно.

Вопрос: Почему ImageView отображает черное изображение и как это решить?

Я использую реализацию фильтра Canny egde, созданного arekolek github

необязательно: могу ли я вычислить его быстрее?

Я закончил весь код, написанный в методе «runEdgeFilter (…)», который запускается, когда я нажимаю изображение на своем устройстве, чтобы убедиться, что я не возился с ImageView в другом месте. Код, который я использую до сих пор.

 import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v8.renderscript.*;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {

    private static final float THRESHOLD_MULT_LOW = 0.66f * 0.00390625f;
    private static final float THRESHOLD_MULT_HIGH = 1.33f * 0.00390625f;

    private ImageView imageView;
    private Bitmap img;
    private boolean setThresholds = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imageView = (ImageView) findViewById(R.id.imageView);
        img = BitmapFactory.decodeResource(getResources(), R.drawable.test_img_no_dpi2);
        imageView.setImageBitmap(img);
    }

    public void imageClicked(View view) {
        runEdgeFilter(img, this);
    }

    private void runEdgeFilter(Bitmap image, Context context) {
        int width = image.getWidth();
        int height = image.getHeight();

        RenderScript rs = RenderScript.create(context);

        Allocation allocationIn = Allocation.createFromBitmap(rs, image);

        Type.Builder tb;

        tb = new Type.Builder(rs, Element.F32(rs)).setX(width).setY(height);
        Allocation allocationBlurred = Allocation.createTyped(rs, tb.create());
        Allocation allocationMagnitude = Allocation.createTyped(rs, tb.create());

        tb = new Type.Builder(rs, Element.I32(rs)).setX(width).setY(height);
        Allocation allocationDirection = Allocation.createTyped(rs, tb.create());
        Allocation allocationEdge = Allocation.createTyped(rs, tb.create());

        tb = new Type.Builder(rs, Element.I32(rs)).setX(256);
        Allocation allocationHistogram = Allocation.createTyped(rs, tb.create());

        tb = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
        Allocation allocationOut = Allocation.createTyped(rs, tb.create());

        ScriptC_edge edgeFilter = new ScriptC_edge(rs);

        ScriptIntrinsicHistogram histogram = ScriptIntrinsicHistogram.create(rs, Element.U8(rs));

        histogram.setOutput(allocationHistogram);

        edgeFilter.invoke_set_histogram(allocationHistogram);
        edgeFilter.invoke_set_blur_input(allocationIn);
        edgeFilter.invoke_set_compute_gradient_input(allocationBlurred);
        edgeFilter.invoke_set_suppress_input(allocationMagnitude, allocationDirection);
        edgeFilter.invoke_set_hysteresis_input(allocationEdge);
        edgeFilter.invoke_set_thresholds(0.2f, 0.6f);

        histogram.forEach_Dot(allocationIn);

        int[] histogramOutput = new int[256];

        allocationHistogram.copyTo(histogramOutput);


        if(setThresholds) {
            int median = width * height / 2;
            for (int i = 0; i < 256;   i) {
                median -= histogramOutput[i];
                if (median < 1) {
                    edgeFilter.invoke_set_thresholds(i * THRESHOLD_MULT_LOW, i * THRESHOLD_MULT_HIGH);
                    break;
                }
            }
        }

        edgeFilter.forEach_blur(allocationBlurred);
        edgeFilter.forEach_compute_gradient(allocationMagnitude);
        edgeFilter.forEach_suppress(allocationEdge);
        edgeFilter.forEach_hysteresis(allocationOut);

        allocationOut.copyTo(image);

        allocationIn.destroy();
        allocationMagnitude.destroy();
        allocationBlurred.destroy();
        allocationDirection.destroy();
        allocationEdge.destroy();
        allocationHistogram.destroy();
        allocationOut.destroy();
        histogram.destroy();
        edgeFilter.destroy();
        rs.destroy();

        imageView.setImageBitmap(image);
    }
}
  

renderscript edge.rs:

 #pragma version(1)
#pragma rs java_package_name(com.lukasz.edgeexamplers)
#pragma rs_fp_relaxed

#include "rs_debug.rsh"

static rs_allocation raw, magnitude, blurred, direction, candidates;
static float low, high;
static const uint32_t zero = 0;

void set_blur_input(rs_allocation u8_buf) {
    raw = u8_buf;
}

void set_compute_gradient_input(rs_allocation f_buf) {
    blurred = f_buf;
}

void set_suppress_input(rs_allocation f_buf, rs_allocation i_buf) {
    magnitude = f_buf;
    direction = i_buf;
}

void set_hysteresis_input(rs_allocation i_buf) {
    candidates = i_buf;
}

void set_thresholds(float l, float h) {
    low = l;
    high = h;
}

inline static float getElementAt_uchar_to_float(rs_allocation a, uint32_t x,
        uint32_t y) {
    return rsGetElementAt_uchar(a, x, y) / 255.0f;
}

static rs_allocation histogram;

void set_histogram(rs_allocation h) {
    histogram = h;
}

uchar4 __attribute__((kernel)) addhisto(uchar in, uint32_t x, uint32_t y) {
    int px = (x - 100) / 2;
    if (px > -1 amp;amp; px < 256) {
        int v = log((float) rsGetElementAt_int(histogram, (uint32_t) px)) * 30;
        int py = (400 - y);
        if (py > -1 amp;amp; v > py) {
            in = 255;
        }
        if (py == -1) {
            in = 255;
        }
    }
    uchar4 out = { in, in, in, 255 };
    return out;
}

uchar4 __attribute__((kernel)) copy(uchar in) {
    uchar4 out = { in, in, in, 255 };
    return out;
}

uchar4 __attribute__((kernel)) blend(uchar4 in, uint32_t x, uint32_t y) {
    uchar r = rsGetElementAt_uchar(raw, x, y);
    uchar4 out = { r, r, r, 255 };
    return max(out, in);
}

float __attribute__((kernel)) blur(uint32_t x, uint32_t y) {
    float pixel = 0;

    pixel  = 2 * getElementAt_uchar_to_float(raw, x - 2, y - 2);
    pixel  = 4 * getElementAt_uchar_to_float(raw, x - 1, y - 2);
    pixel  = 5 * getElementAt_uchar_to_float(raw, x, y - 2);
    pixel  = 4 * getElementAt_uchar_to_float(raw, x   1, y - 2);
    pixel  = 2 * getElementAt_uchar_to_float(raw, x   2, y - 2);

    pixel  = 4 * getElementAt_uchar_to_float(raw, x - 2, y - 1);
    pixel  = 9 * getElementAt_uchar_to_float(raw, x - 1, y - 1);
    pixel  = 12 * getElementAt_uchar_to_float(raw, x, y - 1);
    pixel  = 9 * getElementAt_uchar_to_float(raw, x   1, y - 1);
    pixel  = 4 * getElementAt_uchar_to_float(raw, x   2, y - 1);

    pixel  = 5 * getElementAt_uchar_to_float(raw, x - 2, y);
    pixel  = 12 * getElementAt_uchar_to_float(raw, x - 1, y);
    pixel  = 15 * getElementAt_uchar_to_float(raw, x, y);
    pixel  = 12 * getElementAt_uchar_to_float(raw, x   1, y);
    pixel  = 5 * getElementAt_uchar_to_float(raw, x   2, y);

    pixel  = 4 * getElementAt_uchar_to_float(raw, x - 2, y   1);
    pixel  = 9 * getElementAt_uchar_to_float(raw, x - 1, y   1);
    pixel  = 12 * getElementAt_uchar_to_float(raw, x, y   1);
    pixel  = 9 * getElementAt_uchar_to_float(raw, x   1, y   1);
    pixel  = 4 * getElementAt_uchar_to_float(raw, x   2, y   1);

    pixel  = 2 * getElementAt_uchar_to_float(raw, x - 2, y   2);
    pixel  = 4 * getElementAt_uchar_to_float(raw, x - 1, y   2);
    pixel  = 5 * getElementAt_uchar_to_float(raw, x, y   2);
    pixel  = 4 * getElementAt_uchar_to_float(raw, x   1, y   2);
    pixel  = 2 * getElementAt_uchar_to_float(raw, x   2, y   2);

    pixel /= 159;

    return pixel;
}

float __attribute__((kernel)) compute_gradient(uint32_t x, uint32_t y) {
    float gx = 0;

    gx -= rsGetElementAt_float(blurred, x - 1, y - 1);
    gx -= rsGetElementAt_float(blurred, x - 1, y) * 2;
    gx -= rsGetElementAt_float(blurred, x - 1, y   1);
    gx  = rsGetElementAt_float(blurred, x   1, y - 1);
    gx  = rsGetElementAt_float(blurred, x   1, y) * 2;
    gx  = rsGetElementAt_float(blurred, x   1, y   1);

    float gy = 0;

    gy  = rsGetElementAt_float(blurred, x - 1, y - 1);
    gy  = rsGetElementAt_float(blurred, x, y - 1) * 2;
    gy  = rsGetElementAt_float(blurred, x   1, y - 1);
    gy -= rsGetElementAt_float(blurred, x - 1, y   1);
    gy -= rsGetElementAt_float(blurred, x, y   1) * 2;
    gy -= rsGetElementAt_float(blurred, x   1, y   1);

    int d = ((int) round(atan2pi(gy, gx) * 4.0f)   4) % 4;
    rsSetElementAt_int(direction, d, x, y);
    return hypot(gx, gy);
}

int __attribute__((kernel)) suppress(uint32_t x, uint32_t y) {
    int d = rsGetElementAt_int(direction, x, y);
    float g = rsGetElementAt_float(magnitude, x, y);
    if (d == 0) {
        // horizontal, check left and right
        float a = rsGetElementAt_float(magnitude, x - 1, y);
        float b = rsGetElementAt_float(magnitude, x   1, y);
        return a < g amp;amp; b < g ? 1 : 0;
    } else if (d == 2) {
        // vertical, check above and below
        float a = rsGetElementAt_float(magnitude, x, y - 1);
        float b = rsGetElementAt_float(magnitude, x, y   1);
        return a < g amp;amp; b < g ? 1 : 0;
    } else if (d == 1) {
        // NW-SE
        float a = rsGetElementAt_float(magnitude, x - 1, y - 1);
        float b = rsGetElementAt_float(magnitude, x   1, y   1);
        return a < g amp;amp; b < g ? 1 : 0;
    } else {
        // NE-SW
        float a = rsGetElementAt_float(magnitude, x   1, y - 1);
        float b = rsGetElementAt_float(magnitude, x - 1, y   1);
        return a < g amp;amp; b < g ? 1 : 0;
    }
}

static const int NON_EDGE = 0b000;
static const int LOW_EDGE = 0b001;
static const int MED_EDGE = 0b010;
static const int HIG_EDGE = 0b100;

inline static int getEdgeType(uint32_t x, uint32_t y) {
    int e = rsGetElementAt_int(candidates, x, y);
    float g = rsGetElementAt_float(magnitude, x, y);
    if (e == 1) {
        if (g < low)
            return LOW_EDGE;
        if (g > high)
            return HIG_EDGE;
        return MED_EDGE;
    }
    return NON_EDGE;
}

uchar4 __attribute__((kernel)) hysteresis(uint32_t x, uint32_t y) {
    uchar4 white = { 255, 255, 255, 255 };
    uchar4 red = { 255, 0, 0, 255 };
    uchar4 black = { 0, 0, 0, 255 };
    int type = getEdgeType(x, y);
    if (type) {
        if (type amp; LOW_EDGE) {
            return black;
        }
        if (type amp; HIG_EDGE) {
            //rsDebug("wh : x=", x);
            //rsDebug("wh : y=", y);
            return white;
        }

        // it's medium, check nearest neighbours
        type = getEdgeType(x - 1, y - 1);
        type |= getEdgeType(x, y - 1);
        type |= getEdgeType(x   1, y - 1);
        type |= getEdgeType(x - 1, y);
        type |= getEdgeType(x   1, y);
        type |= getEdgeType(x - 1, y   1);
        type |= getEdgeType(x, y   1);
        type |= getEdgeType(x   1, y   1);

        if (type amp; HIG_EDGE) {
            //rsDebug("wh : x=", x);
            //rsDebug("wh : y=", y);
            return white;
        }

        if (type amp; MED_EDGE) {
            // check further
            type = getEdgeType(x - 2, y - 2);
            type |= getEdgeType(x - 1, y - 2);
            type |= getEdgeType(x, y - 2);
            type |= getEdgeType(x   1, y - 2);
            type |= getEdgeType(x   2, y - 2);
            type |= getEdgeType(x - 2, y - 1);
            type |= getEdgeType(x   2, y - 1);
            type |= getEdgeType(x - 2, y);
            type |= getEdgeType(x   2, y);
            type |= getEdgeType(x - 2, y   1);
            type |= getEdgeType(x   2, y   1);
            type |= getEdgeType(x - 2, y   2);
            type |= getEdgeType(x - 1, y   2);
            type |= getEdgeType(x, y   2);
            type |= getEdgeType(x   1, y   2);
            type |= getEdgeType(x   2, y   2);

            if (type amp; HIG_EDGE) {
                //rsDebug("wh : x=", x);
                //rsDebug("wh : y=", y);
                return white;
            }
        }
    }
    return black;
}
  

После некоторой отладки я обнаружил, что:

 uchar4 __attribute__((kernel)) hysteresis(uint32_t x, uint32_t y) {...}
  

возвращает белые и черные пиксели, поэтому, я думаю, renderscript работает правильно.
Вывод имеет тот же тип, что и мои предыдущие фильтры renderscript (uchar4), которые я успешно назначаю Bitmap.
Я понятия не имею, что я сделал не так.

Также мой logcat печатает:

 V/RenderScript_jni: RS compat mode 
V/RenderScript_jni: Unable to load libRSSupportIO.so, USAGE_IO not supported
V/RenderScript_jni: Unable to load BLAS lib, ONLY BNNM will be supported: java.lang.UnsatisfiedLinkError: Couldn't load blasV8 from loader dalvik.system.PathClassLoader[dexPath=/data/app/com.lukasz.edgeexamplers-20.apk,libraryPath=/data/app-lib/com.lukasz.edgeexamplers-20]: findLibrary returned null
E/RenderScript: Couldn't load libRSSupportIO.so
  

в каждой программе, использующей renderscript, но другие программы работают даже с этими предупреждениями.

Обновление # 1

Как упоминал @Stephen Hines, возникла проблема с чтением за пределами границ. Я думаю, что я исправил это на данный момент (не связываясь с renderscript), изменив эти строки:

 edgeFilter.forEach_blur(allocationBlurred);
edgeFilter.forEach_compute_gradient(allocationMagnitude);
edgeFilter.forEach_suppress(allocationEdge);
edgeFilter.forEach_hysteresis(allocationOut);
  

в:

 Script.LaunchOptions sLaunchOpt = new Script.LaunchOptions();
sLaunchOpt.setX(2, width - 3);
sLaunchOpt.setY(2, height - 3);
edgeFilter.forEach_blur(allocationBlurred, sLaunchOpt);
edgeFilter.forEach_compute_gradient(allocationMagnitude, sLaunchOpt);
edgeFilter.forEach_suppress(allocationEdge, sLaunchOpt);
edgeFilter.forEach_hysteresis(allocationOut, sLaunchOpt);
  

Но моя проблема все еще не решена. Вывод черный, как и ранее.

Комментарии:

1. Одна из проблем заключается в том, что вы определенно читаете за пределами своих «x-1» и «x-2» обращений в вашем ядре. Вам нужно зафиксировать границу вашего фактического изображения, чтобы сделать это правильно. Я не смотрел дальше, потому что вы не объясняете, что на самом деле неверно (или, может быть, это просто сбой для вас из-за проблем с границами). Вы можете попробовать включить контекст ОТЛАДКИ для получения некоторой помощи ( developer.android.com/reference/android/renderscript /… ). Что касается журналов предупреждений, они действительно безвредны. Вам не нужна поддержка BLAS или ввода-вывода.

2. Спасибо за ваш комментарий. Использование RenderScript rs = RenderScript.create(context, RenderScript.ContextType.DEBUG); не добавило ничего нового в logcat. Также я заставил renderscript работать с обрезанным изображением, что должно устранить проблему с границами.

3. Я предполагаю, что вы используете одноканальное входное растровое изображение. В этом случае вам, вероятно, следует вызывать histogram.forEach() вместо histogram.forEach_Dot(), потому что последний будет умножать каждый пиксель на 0,299f, значение красной точки по умолчанию.