Renderscript 中的直方图匹配
Histogram Matching in Renderscript
为了对齐两个灰度图像的强度值(作为进一步处理的第一步)我写了一个 Java 方法:
将两个图像的位图转换为两个包含位图强度的 int[]
数组(我在这里只取红色分量,因为它是灰度,即 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 的累积强度分布与位图 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;
}
将 int[]
数组转换回位图。
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;
}
虽然核心过程 2) 简单且快速,但转换步骤 1) 和 3) 的效率相当低。在 Renderscript 中完成所有事情会更酷。但是,老实说,由于缺少文档,我完全迷失了这样做,虽然有许多关于 Renderscript 可以执行的令人印象深刻的示例,但我看不到从这些可能性中受益的方法(没有书籍,没有文档)。非常感谢任何建议!
作为起点,使用 Android Studio 到 "Import Sample..." 和 select 基本渲染脚本。这将为您提供一个我们现在要修改的工作项目。
首先,让我们向 MainActivity
添加更多分配引用。我们将使用它们在 Java 和 Renderscript 之间传递图像数据、直方图和 LUT。
private Allocation mInAllocation;
private Allocation mInAllocation2;
private Allocation[] mOutAllocations;
private Allocation mHistogramAllocation;
private Allocation mHistogramAllocation2;
private Allocation mLUTAllocation;
然后在 onCreate()
加载另一张图片,您还需要将其添加到 /res/drawables/.
mBitmapIn2 = loadBitmap(R.drawable.cat_480x400);
在 createScript()
中创建额外分配:
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);
现在是主要部分(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]));
夫妇注意事项:
- 在您的代码部分,我还修复了 LUT 分配大小 - 只需要 256 个位置,
- 如您所见,我将累积直方图和 LUT 的计算留在 Java 端。由于数据依赖性和计算规模小,这些很难有效地并行化,但考虑到后者,我认为这不是问题。
最后是Renderscript代码。唯一不明显的部分是使用 rsAtomicInc()
来增加直方图箱中的值 - 这是必要的,因为可能有许多线程试图同时增加同一个箱。
#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);
}
为了对齐两个灰度图像的强度值(作为进一步处理的第一步)我写了一个 Java 方法:
将两个图像的位图转换为两个包含位图强度的
int[]
数组(我在这里只取红色分量,因为它是灰度,即 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 的累积强度分布与位图 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; }
将
int[]
数组转换回位图。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; }
虽然核心过程 2) 简单且快速,但转换步骤 1) 和 3) 的效率相当低。在 Renderscript 中完成所有事情会更酷。但是,老实说,由于缺少文档,我完全迷失了这样做,虽然有许多关于 Renderscript 可以执行的令人印象深刻的示例,但我看不到从这些可能性中受益的方法(没有书籍,没有文档)。非常感谢任何建议!
作为起点,使用 Android Studio 到 "Import Sample..." 和 select 基本渲染脚本。这将为您提供一个我们现在要修改的工作项目。
首先,让我们向 MainActivity
添加更多分配引用。我们将使用它们在 Java 和 Renderscript 之间传递图像数据、直方图和 LUT。
private Allocation mInAllocation;
private Allocation mInAllocation2;
private Allocation[] mOutAllocations;
private Allocation mHistogramAllocation;
private Allocation mHistogramAllocation2;
private Allocation mLUTAllocation;
然后在 onCreate()
加载另一张图片,您还需要将其添加到 /res/drawables/.
mBitmapIn2 = loadBitmap(R.drawable.cat_480x400);
在 createScript()
中创建额外分配:
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);
现在是主要部分(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]));
夫妇注意事项:
- 在您的代码部分,我还修复了 LUT 分配大小 - 只需要 256 个位置,
- 如您所见,我将累积直方图和 LUT 的计算留在 Java 端。由于数据依赖性和计算规模小,这些很难有效地并行化,但考虑到后者,我认为这不是问题。
最后是Renderscript代码。唯一不明显的部分是使用 rsAtomicInc()
来增加直方图箱中的值 - 这是必要的,因为可能有许多线程试图同时增加同一个箱。
#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);
}