2

In order to align the intensity values of two grayscale Images (as a first step for further processing) I wrote a Java method that:

  1. converts the bitmaps of the two images into two int[] arrays containing the bitmap's intensities (I just take the red component here, since it's grayscale, i.e. r=g=b ).

    public static int[] bmpToData(Bitmap bmp){
    int width = bmp.getWidth();
    int height = bmp.getHeight();
    int anzpixel = width*height;
    int [] pixels = new int[anzpixel];
    int [] data = new int[anzpixel];  
    bmp.getPixels(pixels, 0, width, 0, 0, width, height);
    for (int i = 0 ; i < anzpixel ; i++) {
    int p = pixels[i];
    int r = (p & 0xff0000) >> 16;
    //int g = (p & 0xff00) >> 8;
    //int b = p & 0xff;
    data[i] = r;
    }
    return data;
    }
    
  2. aligns the cumulated intensity distributions of Bitmap 2 to that of Bitmap 1

        //aligns the intensity distribution of a grayscale picture moving    (given by int[] //data2) the the intensity distribution of a reference picture fixed (given by // int[] data1)
    public static int[] histMatch(int[] data1, int[] data2){
    
       int anzpixel = data1.length;
       int[] histogram_fixed = new int[256];
       int[] histogram_moving = new int[256];
       int[] cumhist_fixed = new int[256];
       int[] cumhist_moving = new int[256];
       int i=0;
       int j=0;
    
       //read intensities of fixed und moving in histogram
       for (int n = 0; n < anzpixel; n++) {
          histogram_fixed[data1[n]]++;
          histogram_moving[data2[n]]++;
       }
    
       // calc cumulated distributions
       cumhist_fixed[0]=histogram_fixed[0];
       cumhist_moving[0]=histogram_moving[0];
       for ( i=1; i < 256; ++i ) {
          cumhist_fixed[i] = cumhist_fixed[i-1]+histogram_fixed[i];
          cumhist_moving[i] = cumhist_moving[i-1]+histogram_moving [i];
       }
    
       // look-up-table lut[]. For each quantile i of the moving picture search     the 
       // value j of the fixed picture where the quantile is the same as that of moving   
       int[] lut = new int[anzpixel];
       j=0;
       for ( i=0; i < 256; ++i ){
          while(cumhist_fixed[j]< cumhist_moving[i]){
             j++;
          }
    
          // check, whether the distance to the next-lower intensity is even lower, and if so, take this value
          if ((j!=0) && ((cumhist_fixed[j-1]- cumhist_fixed[i]) < (cumhist_fixed[j]- cumhist_fixed[i]))){
             lut[i]= (j-1);
          }
          else {
             lut[i]= (j);
          }
       }
    
       // apply the lut[] to moving picture.
       i=0;
       for (int n = 0; n < anzpixel; n++) {
          data2[n]=(int) lut[data2[n]];
       }
       return data2;
    }
    
  3. converts the int[] arrays back to Bitmap.

    public static Bitmap dataToBitmap(int[] data, int width, int heigth) {
     int index=0;
     Bitmap bmp = Bitmap.createBitmap(width, heigth, Bitmap.Config.ARGB_8888);
     for (int x = 0; x < width; x++) {
      for (int y = 0; y < heigth; y++) {
         index=y*width+x;
         int c = data[index];
         bmp.setPixel(x,y,Color.rgb(c, c, c));
         }
     }
     return bmp;
    }
    

While the core procedure 2) is straightforward and fast, the conversion steps 1) and 3) are rather inefficient. It would be more than cool to do the whole thing in Renderscript. But, honestly, I am completely lost in doing so because of missing documentation and, while there are many impressing examples on what Renderscript COULD perform, I don't see a way to benefit from these possibilities (no books, no docu). Any advice is highly appreciated!

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Settembrini
  • 1,366
  • 3
  • 20
  • 32
  • 1
    What exactly is your question? – kittykittybangbang Aug 01 '15 at 00:51
  • At least show us your Java code, so that we know what you want to implement in Renderscript. – Miloslaw Smyk Aug 01 '15 at 07:25
  • Sorry for being imprecise. I have included the code above, but unfortunately didn't manage to get the layout correct. In a nutshell, the problem is as follows. Instead of first converting two Bitmaps into int[] arrays (1.), doing the actual histogram matching of the two int[] arrays (2.) and convert the (matched) int [] array back to Bitmap (3.), I want to do all that in a Renderscript as one procedure, i.e. 2 Bitmaps in and 1 Bitmap (the matched one) out. – Settembrini Aug 01 '15 at 10:28

1 Answers1

2

As a starting point, use Android Studio to "Import Sample..." and select Basic Render Script. This will give you a working project that we will now modify.

First, let's add more Allocation references to MainActivity. We will use them to communicate image data, histograms and the LUT between Java and Renderscript.

private Allocation mInAllocation;
private Allocation mInAllocation2;
private Allocation[] mOutAllocations;
private Allocation mHistogramAllocation;
private Allocation mHistogramAllocation2;
private Allocation mLUTAllocation;

Then in onCreate() load another image, which you will also need to add to /res/drawables/.

    mBitmapIn2 = loadBitmap(R.drawable.cat_480x400);

In createScript() create additional allocations:

    mInAllocation2 = Allocation.createFromBitmap(mRS, mBitmapIn2);
    mHistogramAllocation = Allocation.createSized(mRS, Element.U32(mRS), 256);
    mHistogramAllocation2 = Allocation.createSized(mRS, Element.U32(mRS), 256);
    mLUTAllocation = Allocation.createSized(mRS, Element.U32(mRS), 256);

And now the main part (in RenderScriptTask):

            /*
             * Invoke histogram kernel for both images
             */
            mScript.bind_histogram(mHistogramAllocation);
            mScript.forEach_compute_histogram(mInAllocation);

            mScript.bind_histogram(mHistogramAllocation2);
            mScript.forEach_compute_histogram(mInAllocation2);


            /*
             * Variables copied verbatim from your code.
             */
            int []histogram_fixed = new int[256];
            int []histogram_moving = new int[256];
            int[] cumhist_fixed = new int[256];
            int[] cumhist_moving = new int[256];
            int i=0;
            int j=0;

            // copy computed histograms to Java side
            mHistogramAllocation.copyTo(histogram_fixed);
            mHistogramAllocation2.copyTo(histogram_moving);

            // your code again...
            // calc cumulated distributions
            cumhist_fixed[0]=histogram_fixed[0];
            cumhist_moving[0]=histogram_moving[0];

            for ( i=1; i < 256; ++i ) {
                cumhist_fixed[i] = cumhist_fixed[i-1]+histogram_fixed[i];
                cumhist_moving[i] = cumhist_moving[i-1]+histogram_moving [i];
            }

            // look-up-table lut[]. For each quantile i of the moving picture search     the
            // value j of the fixed picture where the quantile is the same as that of moving
            int[] lut = new int[256];
            j=0;
            for ( i=0; i < 256; ++i ){
                while(cumhist_fixed[j]< cumhist_moving[i]){
                    j++;
                }

                // check, whether the distance to the next-lower intensity is even lower, and if so, take this value
                if ((j!=0) && ((cumhist_fixed[j-1]- cumhist_fixed[i]) < (cumhist_fixed[j]- cumhist_fixed[i]))){
                    lut[i]= (j-1);
                }
                else {
                    lut[i]= (j);
                }
            }

            // copy the LUT to Renderscript side
            mLUTAllocation.copyFrom(lut);
            mScript.bind_LUT(mLUTAllocation);

            // Apply LUT to the destination image
            mScript.forEach_apply_histogram(mInAllocation2, mInAllocation2);


            /*
             * Copy to bitmap and invalidate image view
             */
            //mOutAllocations[index].copyTo(mBitmapsOut[index]);

            // copy back to Bitmap in preparation for viewing the results
            mInAllocation2.copyTo((mBitmapsOut[index]));

Couple notes:

  • In your part of the code I also fixed LUT allocation size - only 256 locations are needed,
  • As you can see, I left the computation of cumulative histogram and LUT on Java side. These are rather difficult to efficiently parallelize due to data dependencies and small scale of the calculations, but considering the latter I don't think it's a problem.

Finally, the Renderscript code. The only non-obvious part is the use of rsAtomicInc() to increase values in histogram bins - this is necessary due to potentially many threads attempting to increase the same bin concurrently.

#pragma version(1)
#pragma rs java_package_name(com.example.android.basicrenderscript)
#pragma rs_fp_relaxed

int32_t *histogram;
int32_t *LUT;

void __attribute__((kernel)) compute_histogram(uchar4 in)
{
    volatile int32_t *addr = &histogram[in.r];
    rsAtomicInc(addr);
}

uchar4 __attribute__((kernel)) apply_histogram(uchar4 in)
{
    uchar val = LUT[in.r];
    uchar4 result;
    result.r = result.g = result.b = val;
    result.a = in.a;

    return(result);
}
Miloslaw Smyk
  • 1,305
  • 10
  • 15
  • Thanks a lot Laubzega for this awesome work. I see, that you use two kernels, one for reading-in the histograms, one for mapping the histogram of image2 to the derived LUT. This is the conceptual solution I was looking for. Unfortunately, when implementing it I am still fighting with some Android Studio Problems. I have set-up a new Project and put the rs-code in a file named histm.rs within a Folder "rs" on the same Level as the Java-Folder. Now, for some reason it does not recognize the initialisation: private ScriptC_histm mScript. – Settembrini Aug 02 '15 at 00:37
  • Does not recognize as in "does not compile" or just "IDE highlights as error"? In general, you also need to modify the project's [Gradle script](http://stackoverflow.com/questions/19658145/how-to-use-the-renderscript-support-library-with-gradle/22976675#22976675). That's why I recommended starting from "File/Import Sample...". – Miloslaw Smyk Aug 02 '15 at 00:48
  • It does not compile and after cleaning the Project it shows a problem with the R-file. I don't see anything special in the gradle script. I tried initially with the Import renderscript example but that did't work because of some interference with the existing code. I think I have just to try some more. – Settembrini Aug 02 '15 at 01:00
  • Since I have no (technical) problem with applying the RS Intrinsics (e.g. I use the blur function in my Project), I wonder whether the above two kernels could also be realised by using the ScriptIntrinsicsLUT function. Unfortunately the documentation of this function is rather poor. – Settembrini Aug 02 '15 at 10:49
  • Just as an update. The Speed consuming step in my above code was the step 3 (conversion from int[] to Bitmap). In the meantime I found a solution that is 20x faster, as follows: public static Bitmap dataToBitmap2(int[] data, int breite, int hoehe) { int index=0; int[] farb = new int[data.length]; for (int i=0;i – Settembrini Aug 02 '15 at 13:41
  • Still looks wasteful to me, you are iterating over all pixels and having as many Color() calls. Why not use Bitmap.setPixels() and do it all at once? – Miloslaw Smyk Aug 02 '15 at 17:16
  • And as for ScriptIntrinsicLUT, you could use it to replace the second kernel. You would just have to set all three LUTs to the same values, is it does the lookup per color channel. – Miloslaw Smyk Aug 02 '15 at 17:43
  • Miloslaw, the graphics.Color.rbg calls do not refer to a Bitmap's Color object but just to the conversion of the each rgb triple into one integer, and the combined integers are then assigned as an int[] array to the new Bitmap, this takes 10 millis for a 600k pixel picture, which is fine for me. As regards to the ScriptIntrinsicLUT I see what you mean and I'd love to use it but I just don't know how to apply, the documentation on that function is too thin (for me). – Settembrini Aug 02 '15 at 20:11
  • This thread is getting unwieldy, post a question about ScriptIntrinsicLUT and I'll try to help. – Miloslaw Smyk Aug 02 '15 at 22:44
  • I set up this again from scatch, including proper rs-Folder by menu new folder. Now I'm so close to get it work, it even accepts the ScriptC_histm. The only thing not being accepted is the R-file (red) and it doesn't Display the layout preview. But now I'm sure, I can get it (I am new to Android but already see that layouts are the biggest intellectual challenge...). Will check the Convolve3x3 tomorrow - thanks so much for that!!! – Settembrini Aug 03 '15 at 20:52
  • As an update on this. I have used today the first time ScriptInstrinsicHistogram (in another context, however). I can say this Function is about 10x faster than anything you can build with own Scripts; so I recommend to use ScriptInstrinsicHistogram for the histogram part of this code. – Settembrini Jul 19 '16 at 21:19