【问题标题】:Scan every pixels to get RGB values扫描每个像素以获取 RGB 值
【发布时间】:2018-05-03 04:23:08
【问题描述】:

我想在视频流运行时获取每个像素的 RGB 值。我目前正在使用 textureView 来显示相机流。每次我得到一个新帧,我都会扫描帧,看看我的 RG 和 B 值是否都等于 255。但是问题是:扫描一帧需要 20 多秒,所以它跳过 30-40 帧。我想读取像素而不跳过任何像素。 因此,如果有人可以让我知道是否有有效的方法来做到这一点,我将非常感谢他们的帮助。

这就是我正在做的。

TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
        openCamera();
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
    Bitmap bitmap = textureView.getBitmap();

        // Initializing all color values
        int redmax = 0, greenmax = 0, bluemax = 0, redx = 0, bluex = 0, greenx = 0, redy = 0, bluey = 0, greeny = 0;

        int Pixel = bitmap.getHeight() * bitmap.getWidth();

        ByteBuffer Pixels = ByteBuffer.allocateDirect(4 * Pixel);

        for (int y = 0; y < bitmap.getHeight(); y++) { // Height
            for (int x = 0; x < bitmap.getWidth(); x++) {    // Width
                GLES20.glReadPixels(x, y, bitmap.getWidth(), bitmap.getHeight(), GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, Pixels);

                byte rValue = Pixels.get(0);
                byte gValue = Pixels.get(1);
                byte bValue = Pixels.get(2);
                byte A = Pixels.get(3);


                if (rValue > redmax) { // This is just to get max red Value in the frame
                    redmax = rValue;
                    redx = x;
                    redy = y;

                }
                if (bValue > bluemax) { // This is just to get max blue Value in the frame
                    bluemax = bValue;
                    bluex = x;
                    bluey = y;
                }

                if (gValue > greenmax) { // This is just to get max green Value in the frame
                    greenmax = gValue;
                    greenx = x;
                    greeny = y;
                }
            }
        }

        Log.i("Picture max red value", "R: " + redmax + " x: " + redx + " y: " + redy);
        Log.i("Picture max green value", "G: " + greenmax + " x: " + greenx + " y: " + greeny);
        Log.i("Picture max blue value", "B: " + bluemax + " x: " + bluex + " y: " + bluey);
        Log.i("Picture x and y value", "XY: " + " x: " + bitmap.getWidth() + " y: " + bitmap.getHeight());

        if ((redmax + bluemax + greenmax) > 760) {
            BitmapUtil.saveBitmap(bitmap, String.format("/sdcard/read_1" + UUID.randomUUID().toString() + ".jpg"));
        } else {
            bitmap.recycle();
        }

【问题讨论】:

    标签: android opengl-es camera android-camera gpu


    【解决方案1】:

    因此,如果我理解正确,您的代码可以正常工作,但您需要对其进行优化。您可能想对您要完成的工作进行完整描述以获得一些想法,但从您的代码中可能会有一些指示。确保您为自己创建一个具有可衡量结果的测试场景,以了解哪些程序优化了您的代码以及优化了多少。

    调用glReadPixels 可能非常耗时,因此请尽量减少它。您在 for 循环中使用它,如果我正确阅读,则读取整个缓冲区大小被 xy 偏移,这意味着它在其他事物旁边溢出。我相信您尝试做的是:GLES20.glReadPixels(x, y, 1, 1 正在读取某个位置的单个像素。这应该会给你带来相当大的性能提升,但正如@Rabbid76 的评论中提到的那样,有人怀疑你会通过读取整个缓冲区然后遍历像素来获得性能。

    一些程序可以直接卸载到 GPU 上,检查哪个是“最大”像素似乎直接像其中之一。您可以创建一个 FBO(帧缓冲区对象)并使用着色器将相同的纹理重新绘制到每个维度中一半大小的缓冲区,以保留较大的值。最后,这将为您提供一个包含您的值的单个像素的缓冲区,但问题是您丢失了该像素的位置(它原来的位置)。

    为了解释一下重绘纹理的过程,假设您有一个 2x2 的纹理,它将被重绘到 1x1 缓冲区。片段着色器将获取 4 个最近的像素并输出最大的像素,如下所示:

    gl_fragColor = vec4(max(t1.r, max(t2.r, max(t3.r, t4.r))), max(t1.g, max(t2.r, max(t3.g, ...
    

    其中tN(0.25, 0.25)(0.75, 0.25)(0.25, 0.75)(0.75, 0.75) 的样本。

    这将仅针对单个片段执行,但之前的调用将使用相同的着色器将 4x4 纹理重绘到具有 4 个片段的 2x2 缓冲区。相同的 16x16 重绘为 4x4...所以您真正需要做的就是将着色器中的这些常量转换为使用制服并计算它们。

    您甚至应该能够将这项工作放在单独的线程上并跳过帧(如果允许并且需要),但成本太高。视频应该仍然可以不间断地播放。

    【讨论】:

    • 嘿@Matic 你能解释一下关于 GPU 使用的更多信息吗?我是新手,所以我需要帮助
    • @Daniel 这可能是一个很多开始。我想我会从 FBO 开始;创建一个新的帧缓冲区和任意大小的纹理。然后绑定此缓冲区并像以前一样将其绘制到主缓冲区。然后在绘制到 FBO 的每一帧上绑定主缓冲区和附加到 FBO 的纹理,并将这个重新绘制到(现在绑定的)主缓冲区。在绘制到 FBO 时使用一些测试着色器(如灰度)只是为了测试结果。
    • @Daniel 在这个 FBO 工作之后,您应该从缩小规模开始。找到纹理的第一个 POT(2 的幂)大小并将其用作初始 FBO 纹理。因此,例如 600x400 将产生 1024x512 纹理。在这种情况下,将此纹理附加到您的 FBO 并使用您需要绘制纹理的视口 (600x400)。现在创建另一个具有相同大小纹理的 FBO 现在将事物从一个缓冲区重绘几次到另一个缓冲区,同时每次将视口缩小一半...让我解释一下...
    • @Daniel 第一个 FBO(将调用 A)现在绘制了 600x400 的纹理,但其自身大小为 1024x512。现在绑定第二个 FBO (B) 并将视口设置为现在 512x256 的一半大小,并在其上从 A 绘制纹理。然后使用 B 中的纹理再次缩小到 256x128 并拉回到 A。然后再次使用 128x64 将绘图 A 纹理交换到 B。目前应该够用了。现在尝试将其绘制回主帧缓冲区以查看结果。使用纹理坐标,因此只显示内部纹理(0,(128/1024)*(600/1024)), (0,(64/512)*(400/512)) 必须产生现在相同的视频但质量低
    • @Daniel 如果你得到这个结果,那么你现在非常接近结果......调整你的着色器,以便你输出源纹理中 4 个附近像素的最大值(我已经描述过在答案中)。确保在纹理上使用nearest 设置,因此不会为您完成插值。现在,不要将纹理从 A 重新绘制到 B 并返回 N 次,直到结果是 1x1 纹理。此时只需绑定您刚刚绘制的缓冲区并读取第一个像素(在 0, 0 处),这应该是您的结果。不要忘记仍然绘制原始视频。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-04-10
    • 2013-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-01
    • 1970-01-01
    相关资源
    最近更新 更多