【问题标题】:Android Renderscript, remove white/whiteish background from BitmapAndroid Renderscript,从位图中删除白色/白色背景
【发布时间】:2016-08-06 10:39:54
【问题描述】:

我正在开发基于图片的应用程序,但由于 Renderscript 的问题而被阻止。

理论上我的目的很简单,我想从用户加载的图像中删除白色背景,以在我设置为背景的另一张图像上显示它们。更具体地说,我想要模拟在纸画布(也是图片)上打印用户上传的图形的效果,并具有逼真的效果。

我不能假设用户能够上传带有 alpha 通道的漂亮 PNG,其中一项要求是使用 JPG 进行操作。

我一直在尝试使用 RenderScripts 解决这个问题,使用类似这样的方法将 alpha 0 设置为 R、G 和 B 都等于或大于 240 的任何值:

#pragma version(1)
#pragma rs java_package_name(mypackagename)
rs_allocation gIn;
rs_allocation gOut;
rs_script gScript;

const static float th = 239.f/256.f;


void root(const uchar4 v_in, uchar4 v_out, const void* usrData, uint32_t x,uint32_t y){
float4 f4 = rsUnpackColor8888(*v_in);


if(f4.r > th  && f4.g > th && f4.b > th)
{
    f4.a = 0;
}


   *v_out =  rsPackColorTo8888(f4);

}

void filter() {
    rsForEach(gScript, gIn, gOut);
}

但结果并不令人满意,主要有两个原因:

  • 如果照片的背景不是白色渐变,则脚本会产生丑陋的噪点效果
  • 阴影靠近边缘的图像在靠近边缘时会产生噪点效果

我知道从 alpha 0 传递到 alpha 1 太极端了,我尝试了不同的解决方案,包括当 R、G、B 分量的总和减少时线性增加 alpha,但我周围仍然有嘈杂的像素和块。

如果是纯白色或常规背景(例如 Google 主页的快照),它可以完美运行,但如果是照片,则远远不能接受。

我认为,如果我能够处理一个“行”像素或一个“块”像素而不是单个像素,则可以更容易检测平坦背景并避免遇到渐变,但我对渲染脚本了解不够。

谁能指出我正确的方向?

PS

我不能使用 PorterDuff 和乘法,因为背景和前景具有不同的尺寸,而且我需要能够在应用效果后在背景画布周围拖动上传的图像。如果我将图像与移动结果图像的背景区域相乘,则会导致背景的一部分也移动。

【问题讨论】:

    标签: android image image-processing bitmap renderscript


    【解决方案1】:

    如果我猜对了,您想根据相邻像素的一行/块来确定当前像素是否是白色背景。

    您可以尝试使用rsGetElementAt。例如,要处理原始代码中的一行:

    #pragma version(1)
    #pragma rs java_package_name(mypackagename)
    rs_allocation gIn;
    rs_allocation gOut;
    rs_script gScript;
    
    const static float th = 239.f/256.f;
    
    void root(const uchar4 v_in, uchar4 v_out, const void* usrData, uint32_t x,uint32_t y){
        float4 f4 = rsUnpackColor8888(*v_in);
        uint32_t width = rsAllocationGetDimX(gIn);
        // E.g: Processing a line from x to x+5.
        bool isBackground = true;
        for (uint32_t i=0; i<=5 && x+i<width; i++) {
            uchar4 nPixel_u4 = rsGetElementAt_uchar4(gIn, x+i, y);
            float4 nPixel_f4 = rsUnpackColor8888(nPixel_u4);
    
            if(nPixel_f4.r <= th  || nPixel_f4.g <= th || nPixel_f4.b <= th) {
                isBackground = false;
                break;
            }
        }
        if (isBackground) {
            f4.a = 0.0f;
            *v_out =  rsPackColorTo8888(f4);
        }
    }
    
    void filter() {
        rsForEach(gScript, gIn, gOut);
    }
    

    这只是一个简单的示例,说明如何使用 rsGetElementAt 从全局分配中的给定位置获取数据。有一个对应的rsSetElementAt 用于将数据保存到全局分配中。我希望它对您的项目有所帮助。

    【讨论】:

    • 谢谢@Miao,看起来很有希望。我正在试一试,我会接受你的回答。我暂时赞成它:)
    • 您也可以考虑放弃通过可调用(即 filter())调用它,而是使用我们提供的反射 Java forEach_root() 函数。您还可以将函数更改为按值传递,因为您根本不使用指针和/或 usrData。