2

few days ago I've just started learning RenderScript. I managed to create some simple image processing filters e.g. grayscale, color change. Now I'm working on Canny edge filters with no success.

Question: Why ImageView displays black image and how to solve it?

I'am using implementation of Canny egde filter made by arekolek github

optional: Can I compute it faster?

I ended with all code wrote in on method "runEdgeFilter(...)" which runs when i clicked image on my device, to make sure I'am not messing with imageView in other place. Code that i use so far.

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 && px < 256) {
        int v = log((float) rsGetElementAt_int(histogram, (uint32_t) px)) * 30;
        int py = (400 - y);
        if (py > -1 && 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 && 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 && 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 && 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 && 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 & LOW_EDGE) {
            return black;
        }
        if (type & 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 & HIG_EDGE) {
            //rsDebug("wh : x=", x);
            //rsDebug("wh : y=", y);
            return white;
        }

        if (type & 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 & HIG_EDGE) {
                //rsDebug("wh : x=", x);
                //rsDebug("wh : y=", y);
                return white;
            }
        }
    }
    return black;
}

After some debugging I found that:

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

returns white and black pixels so renderscript works properly I think. Output is the same type as my previous renderscript filters (uchar4) which I assign to Bitmap with success. I have no idea what I've done wrong.

Also my logcat prints:

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

in every program which use renderscript, but other programs works even with this warnings.

Update #1

As @Stephen Hines mention, there was issue with reading out of bounds. I think I fixed it for now (without messing with renderscript) by changing those lines:

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

into:

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);

But my problem is still not solved. Output is black as earlier.

meshuffle
  • 21
  • 3
  • One issue is that you are definitely reading out of bounds with your "x-1", and "x-2" accesses in your kernel. You need to clamp to the boundary of your actual image to do that properly. I didn't look further because you don't explain what is actually incorrect (or maybe it is just crashing for you because of the bounds issues). You can try turning on DEBUG context for some help (https://developer.android.com/reference/android/renderscript/RenderScript.ContextType.html). As far as the warning logs, they are indeed harmless. You don't need BLAS or IO support. – Stephen Hines Oct 06 '16 at 19:32
  • Thank you for your comment. Using `RenderScript rs = RenderScript.create(context, RenderScript.ContextType.DEBUG);` did not put anything new in logcat. Also I forced renderscript to work on trimmed image which should eliminate bounds issue. – meshuffle Oct 06 '16 at 20:45
  • I assume you are using a single channel input bitmap. In this case you should probably be calling histogram.forEach() instead of histogram.forEach_Dot(), because the latter will multiply each pixel by 0.299f, the default red dot value. – sakridge Oct 09 '16 at 04:15

0 Answers0