【问题标题】:Android Real Time Black-White Threshold ImageAndroid 实时黑白阈值图像
【发布时间】:2014-05-09 00:37:18
【问题描述】:

我有一个代码,可以使用以下代码将灰色位图转换为黑白位图:

// scan through all pixels
for (int x = 0; x < width; ++x) {                                                                                                                                                                                               
  for (int y = 0; y < height; ++y) {                                                                                                                                                                                            
    // get pixel color
    pixel = bitmap.getPixel(x, y);                                                                                                                                                                                              
    A = Color.alpha(pixel);                                                                                                                                                                                                     
    R = Color.red(pixel);                                                                                                                                                                                                       
    G = Color.green(pixel);                                                                                                                                                                                                     
    B = Color.blue(pixel);                                                                                                                                                                                                      
    int gray = (int) (0.2989 * R + 0.5870 * G + 0.1140 * B);                                                                                                                                                                    

    // use 128 as threshold, above -> white, below -> black
    if (gray > 128)
    gray = 255;                                                                                                                                                                                                                 
    else
    gray = 0;                                                                                                                                                                                                                   
    // set new pixel color to output bitmap
    bmOut.setPixel(x, y, Color.argb(A, gray, gray, gray));                                                                                                                                                                      
  }
}   

如您所见,我遍历原始位图的所有像素点,然后将颜色的分量与给定阈值(在本例中为 128)进行比较,然后如果高于我说它是白色的,否则它将是黑色像素。

我现在想做的是一个Spinner,可以改变那个阈值,然后BW图像就会不一样。

为此,我需要重新绘制所有图像,这非常耗费 CPU 时间,再次遍历所有像素需要时间。

有什么方法可以实时使用不同的 BW 阈值更改图像?

有人告诉我使用 GIF,然后我会做什么,只是更改 GIF 的查找表值,有人知道 Android 上的这件事吗?

【问题讨论】:

  • DarkLink,为什么在您的实例中 CPU 时间成本很高?这是因为您在屏幕上绘制位图吗?
  • 只是为了引诱你对我进行幽默,我非常有信心你不需要 GL 或 OpenCV 来做到这一点!但我不想猜测——在某些情况下您可能需要这些工具!最好的。
  • 我不明白汤姆,你需要知道什么?是的,这段代码耗费了 CPU 时间,在将所有像素设置为黑白后,我在屏幕上显示位图。
  • 这就是我需要确认的。您能否将代码准确地发布在屏幕上(例如canvas.drawBitmap(bitmap, 0, 0, paint...) 等...?非常感谢。
  • 多线程怎么样?

标签: android image image-processing bitmap threshold


【解决方案1】:

自从提出这个问题以来已经过了一点时间,但我在寻找其他东西时遇到了这个问题并且碰巧有解决方案。 您可以在没有 OpenCV 或任何其他第三方库的情况下实现此目的,仅使用自 API 级别 1 起可用的 ColorMatrixColorFilter

您可以使用以下矩阵:

//matrix that changes picture into gray scale
public static ColorMatrix createGreyMatrix() {
    ColorMatrix matrix = new ColorMatrix(new float[] {
            0.2989f, 0.5870f, 0.1140f, 0, 0,
            0.2989f, 0.5870f, 0.1140f, 0, 0,
            0.2989f, 0.5870f, 0.1140f, 0, 0,
            0, 0, 0, 1, 0
    });
    return matrix;
}

// matrix that changes gray scale picture into black and white at given threshold.
// It works this way:
// The matrix after multiplying returns negative values for colors darker than threshold
// and values bigger than 255 for the ones higher. 
// Because the final result is always trimed to bounds (0..255) it will result in bitmap made of black and white pixels only
public static ColorMatrix createThresholdMatrix(int threshold) {
    ColorMatrix matrix = new ColorMatrix(new float[] {
            85.f, 85.f, 85.f, 0.f, -255.f * threshold,
            85.f, 85.f, 85.f, 0.f, -255.f * threshold,
            85.f, 85.f, 85.f, 0.f, -255.f * threshold,
            0f, 0f, 0f, 1f, 0f
    });
    return matrix;
}

以下是它们的使用方法:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;

//load source bitmap and prepare destination bitmap
Bitmap pic = BitmapFactory.decodeResource(getResources(), R.drawable.thePicture, options);
Bitmap result = Bitmap.createBitmap(pic.getWidth(), pic.getHeight(),  Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(result);

//first convert bitmap to grey scale:
bitmapPaint.setColorFilter(new ColorMatrixColorFilter(createGreyMatrix()));
c.drawBitmap(pic, 0, 0, bitmapPaint);

//then convert the resulting bitmap to black and white using threshold matrix 
bitmapPaint.setColorFilter(new ColorMatrixColorFilter(createThresholdMatrix(128)));
c.drawBitmap(result, 0, 0, bitmapPaint);

//voilà! You can now draw the result bitmap anywhere You want:
bitmapPaint.setColorFilter(null);
otherCanvas.drawBitmap(result, null, new Rect(x, y, x + size, y + size), bitmapPaint);

希望这会对某人有所帮助。

【讨论】:

  • 这不适用于ARGB_8888。当我使用Bitmap.Config.RGB_565 时,它可以工作,为什么?
  • @user3441905 自从我上次使用 Android 冒险以来已经快一年了,但是当我查看我的旧代码时,我到处都在使用 Bitmap.Config.ARGB_8888 并且代码按预期工作。也许您已经在某处使用 RGB_565 来初始化一些位图,但效果不佳,因为您将 ARGB_8888 和 RGB_565 位图混合在一起?
  • 我不这么认为。我认为您的图像不包含 alpha 通道(即只有 rgb,而不是 argb)。当我使用透明图像时,会导致意想不到的结果。
  • @user3441905 确实如此。两个矩阵都保持 Alpha 通道不变,因此如果位图中有一些透明度,则最终结果实际上应该由黑白半透明像素组成。如果要完全摆脱透明度,可以将阈值矩阵的最后一行更改为“0, 0, 0, 0, 255”(或者“0, 0, 0, 0, 0”现在不记得了如果0 或 255 表示完全透明;稍后再检查)。你的“意外结果”是什么样的?
  • @Arsen 你的矩阵是为ARGB_8888 设置的?
【解决方案2】:

我建议您考虑使用适用于 Android 的 OpenCV 库。安装后,您可以使用它在图像上的许多其他常用操作中进行快速阈值处理。下面的代码片段会将您的颜色 bitamp 转换为灰度图像和灰度 128 的阈值。

// first convert bitmap into OpenCV mat object
Mat imageMat = new Mat (bitmap.getHeight(), bitmap.getWidth(),
                CvType.CV_8U, new Scalar(4));
Bitmap myBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
Utils.bitmapToMat(myBitmap, imageMat);

// now convert to gray
Mat grayMat = new Mat ( bitmap.getHeight(), bitmap.getWidth(),
            CvType.CV_8U, new Scalar(1));
Imgproc.cvtColor(imageMat, grayMat, Imgproc.COLOR_RGB2GRAY, 1);

// get the thresholded image
Mat thresholdMat = new Mat ( bitmap.getHeight(), bitmap.getWidth(),
            CvType.CV_8U, new Scalar(1));
Imgproc.threshold(grayMat, thresholdMat , 128, 255, Imgproc.THRESH_BINARY);

// convert back to bitmap for displaying
Bitmap resultBitmap = Bitmap.createBitmap(bitmap.cols(), bitmap.rows(),
                Bitmap.Config.ARGB_8888);
thresholdMat.convertTo(thresholdMat, CvType.CV_8UC1);
Utils.matToBitmap(thresholdMat, resultBitmap);

【讨论】:

  • 感谢您的回复,这比我发布的代码快吗?快多少?你测试了吗?谢谢;)
  • 我有一个类似的方法,它也通过手动逐个像素地循环图像来处理图像。通过使用 OpenCV 方法,我将速度从大约 5 分钟提高到了几秒钟。该方法涉及的不仅仅是阈值处理,但我会说您可以期望 OpenCV 方法比循环遍历图像并使用 getPixel 单独获取每个像素至少快 100 倍。我根据我测试过的更大的方法改编了上面的代码,但我没有直接测试我提交的代码。
  • 感谢您的回复 JacoFourie,我回家后会测试它;)
  • 嗨@JacoFourie 我现在用你的方式进行了测试,在我的项目中导入了OpenCV库,并使用了你发布的这段代码,但没有注意到性能时间减少:/
  • 我很惊讶您没有看到任何速度提升。您还在循环中的任何地方使用 getPixel 或 setPixel 吗?你正在处理什么尺寸的图像?我处理的图像是 3264x2448 像素。如果您的图像小得多,调用额外库的开销可能会抵消您因避免手动循环而获得的任何速度提升。
【解决方案3】:

GIF 在标题中有一个调色板,它为每个“值”关联与其相关的颜色 (Wikipedia entry)。问题是:过去他们认为 256 种颜色就足够了,这就是调色板条目的数量。您可以GifDecoder.java 类与包含 256 个条目和两个值的本地颜色表一起使用:您的两个输出 0 和 255。但在一天结束时,有一个循环检查 对于每个字节,通过调色板查找关联哪个值 (this is the line)。不比你的代码快一点。

另一种加快速度的方法是使用 OpenGL 来生成阈值(假设无论如何你都要在屏幕上绘制位图,对吧?)。这需要 一堆 额外的代码,幸运的是 this tutorial 完全符合您的要求,减去了阈值部分。

对于阈值处理,需要编译 OpenGL 程序(在 GlRenderer::onSurfaceChanged 上)并每次都使用(在 GlRenderer::onDrawFrame 上)。该程序有两个着色器,它们将以光速对每个像素进行阈值处理! (好吧,也许有点慢,但非常快!)。

着色器如下所示。如果我可以帮助您将它们插入示例代码,请告诉我。

private static final String mVertexShader =
    "attribute vec4 aPosition;\n" +
    "attribute vec4 aTextureCoord;\n" +
    "varying vec2 vTextureCoord;\n" +
    "void main() {\n" +
    "  gl_Position = aPosition;\n" +
    "  vTextureCoord = aTextureCoord;\n" +
    "}\n";

private static final String mFragmentShader =
    "precision mediump float;\n" +
    "varying vec2 vTextureCoord;\n" +
    "uniform float threshold_in_range_0_to_1;\n" +
    "const vec4 coeff_y = vec4(0.256, 0.504, 0.097, 0.0625);\n" +
    "float y;\n" +
    "void main() {\n" +
    "  y = dot(coeff_y, texture2D(sTexture, vTextureCoord));\n" +
    "  if ( y > threshold_in_range_0_to_1)" +
    "    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0)\n" +
    "  else" +
    "    gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0)\n" +
    "}\n";

【讨论】:

  • 感谢您的回复,但这对我来说有点难。我从来没有在 Android 上使用过 OpenGL,我不知道如何把它放在一起:/你能提供进一步的帮助或者更新答案吗?提前非常感谢..
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-11-12
  • 1970-01-01
  • 2023-03-14
  • 2017-10-07
  • 2018-06-09
  • 2015-11-23
  • 2019-03-15
相关资源
最近更新 更多