将位图中除特定颜色外的所有颜色转换为白色

6
我正在使用tess-two库,并希望将图像中除黑色外的所有颜色转换为白色(黑色是文本)。这样做可以使tess-two更容易地读取文本。我尝试了各种方法,但它们逐像素地转换太耗费时间。是否有一种通过canvas或其他快速获得结果的方式来实现这一目标。
更新:
这个算法出现的另一个问题是打印机不使用与Android相同的黑白色。因此,该算法将整个图片转换为白色。
我目前正在使用逐像素方法。
 binarizedImage = convertToMutable(cropped);// the bitmap is made mutable
 int width = binarizedImage.getWidth();
 int height = binarizedImage.getHeight();
 int[] pixels = new int[width * height];
 binarizedImage.getPixels(pixels, 0, width, 0, 0, width, height);

 for(int i=0;i<binarizedImage.getWidth();i++) {
     for(int c=0;c<binarizedImage.getHeight();c++) {
         int pixel = binarizedImage.getPixel(i, c);
         if(!(pixel == Color.BLACK  || pixel == Color.WHITE))
         {
              int index = c * width + i;
             pixels[index] = Color.WHITE;
             binarizedImage.setPixels(pixels, 0, width, 0, 0, width, height);
          }
     }
 }

1
你可以使用 ColorMatrix 吗?这是一个用于转换位图的颜色和透明度组件的 4x5 矩阵。 - Blackbelt
谢谢你的评论,@Blackbelt。但是你能否提供一些帮助来理解ColorMatrix如何有用呢? - Rishabh Lashkari
最起码修复你的循环。认真点,你遍历了像素的大小 (宽度*高度),并简单地使用 if(pixel[i] == Color.BLACK) continue 然后将像素设置为白色。无需考虑其尺寸。这是一个整数数组,你想对所有元素执行此操作。 - Tatarize
你可以在 RenderScript 中更快地完成此操作,这个操作将是相当简单的,并且会在 GPU 上完成所有工作。但是,我也认为 Rishabh 是正确的,它应该能够在 ColorMatrix 中完成。 - Tatarize
3个回答

1

Per, Rishabh的评论。使用颜色矩阵。由于黑色是RGB(0,0,0,255),因此不会受到乘法的影响。因此,如果在所有通道中将所有内容乘以255,则所有内容都将超出限制并被压缩为白色,除了黑色将保持黑色。

       ColorMatrix bc = new ColorMatrix(new float[] {
                255, 255, 255, 0, 0,
                255, 255, 255, 0, 0,
                255, 255, 255, 0, 0,
                0, 0, 0, 1, 0,
        });
        ColorMatrixColorFilter filter = new ColorMatrixColorFilter(bc);
        paint.setColorFilter(filter);

你可以使用这种颜色矩阵滤镜来绘制只有黑色保持不变的位图。
注意:这是一个快速而神奇的技巧,但是它仅适用于黑色。虽然它非常适合您的使用,并将使那个冗长的操作变成即时操作,但它实际上并不符合标题问题“特定颜色”的要求。我的算法可以在任何你想要的颜色中工作,只要它是黑色。

这是一个很棒的答案,并且它也非常有效,但问题是在打印后黑色不是完全的黑色,因为打印机每次打印的黑色量不同。再次感谢,它与原始图片一起使用效果非常好。 - Rishabh Lashkari

0

虽然@Tatarize的答案很完美,但我在阅读印刷图像时遇到了麻烦,因为它并不总是墨黑色。

我在stackoverflow上找到了这个算法,它非常有效,实际上检查特定像素是否更接近黑色或白色,并将像素转换为最接近的颜色。因此提供了范围内的二值化。( https://dev59.com/w3HYa4cB1Zd3GeqPPLsL#16534187 )。

现在我正在将不需要的区域保持在浅色中,而将文本保持在黑色中。这个算法在大约20-35秒内给出了二值化图像。虽然不是那么快,但效率很高。

private static boolean shouldBeBlack(int pixel) {
        int alpha = Color.alpha(pixel);
        int redValue = Color.red(pixel);
        int blueValue = Color.blue(pixel);
        int greenValue = Color.green(pixel);
        if(alpha == 0x00) //if this pixel is transparent let me use TRASNPARENT_IS_BLACK
            return TRASNPARENT_IS_BLACK;

        // distance from the white extreme
        double distanceFromWhite = Math.sqrt(Math.pow(0xff - redValue, 2) + Math.pow(0xff - blueValue, 2) + Math.pow(0xff - greenValue, 2));

        // distance from the black extreme 
        double distanceFromBlack = Math.sqrt(Math.pow(0x00 - redValue, 2) + Math.pow(0x00 - blueValue, 2) + Math.pow(0x00 - greenValue, 2));

       // distance between the extremes
        double distance = distanceFromBlack + distanceFromWhite;

        return ((distanceFromWhite/distance)>SPACE_BREAKING_POINT);
    }

如果返回值为true,则将像素转换为黑色,否则将其转换为白色。
我知道可能会有更好/更快的答案,欢迎更多回答 :)

如果你不使用那个可怕的欧几里得距离,你将显著提高速度。不要使用幂函数来计算redValue * redValue。此外,不要费心使用昂贵的sqrt函数。欧几里得颜色的平方仍然是一种颜色距离算法,与真正的欧几里得颜色距离一样好。 - Tatarize
距离黑色的距离 = 红色值 * 红色值 + 绿色值 * 绿色值 + 蓝色值 * 蓝色值 - Tatarize
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Tatarize

0

同样的事情但是使用RenderScript,大约需要60-100毫秒。您甚至不会注意到延迟。

    Bitmap blackbitmap = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(),bitmap.getConfig());
    RenderScript mRS = RenderScript.create(TouchEmbroidery.activity);
    ScriptC_blackcheck script = new ScriptC_blackcheck(mRS);

    Allocation allocationRaster0 = Allocation.createFromBitmap(
            mRS,
            bitmap,
            Allocation.MipmapControl.MIPMAP_NONE,
            Allocation.USAGE_SCRIPT
    );
    Allocation allocationRaster1 = Allocation.createTyped(mRS, allocationRaster0.getType());
    script.forEach_root(allocationRaster0, allocationRaster1);
    allocationRaster1.copyTo(blackbitmap);

这个分配使用renderscript将数据写入黑色位图。

#pragma version(1)
#pragma rs java_package_name(<YOUR PACKAGENAME GOES HERE>)

void root(const uchar4 *v_in, uchar4 *v_out) {
    uint32_t value = (v_in->r * v_in->r);
    value = value + (v_in->g * v_in->g);
    value = value + (v_in->b * v_in->b);
    if (value > 1200) {
        v_out->r = 255;
        v_out->g = 255;
        v_out->b = 255;
    }
    else {
        v_out->r = 0;
        v_out->g = 0;
        v_out->b = 0;
    }
    v_out->a = 0xFF;
}

注意,1200只是我使用的阈值,应该是所有三个组件都小于20(或者像0、0、sqrt(1200)即(~34)这样)。您可以根据需要将1200的限制上调或下调。

而且build.gradle文件需要Renderscript支持:

renderscriptTargetApi 22

构建工具的最后几个版本声称已经解决了许多渲染脚本的问题。因此,在像您这样的关键任务场所进行此类操作可能是完全合理的。等待20秒太长,60毫秒则不是。


虽然使用ScriptIntrinsicLUT可能不难实现这一点。而且实际上可能会更快(甚至不需要进行乘法运算)。 - Tatarize

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接