Android OpenGLES绘制yuv420纹理

2023-11-04

Android OpenGLES绘制yuv420纹理

96 曾大稳丶 关注

2018.07.16 11:31 字数 76 阅读 440评论 0喜欢 3

  1. 把shader代码写入raw里面

vertex_shader.glsl

attribute vec4 av_Position;//顶点位置
attribute vec2 af_Position;//纹理位置
varying vec2 v_texPo;//纹理位置  与fragment_shader交互
void main() {
    v_texPo = af_Position;
    gl_Position = av_Position;
}


fragment_shader.glsl

precision mediump float;//精度 为float
varying vec2 v_texPo;//纹理位置  接收于vertex_shader
uniform sampler2D sampler_y;//纹理y
uniform sampler2D sampler_u;//纹理u
uniform sampler2D sampler_v;//纹理v

void main() {
    //yuv420->rgb
    float y,u,v;
    y = texture2D(sampler_y,v_texPo).r;
    u = texture2D(sampler_u,v_texPo).r- 0.5;
    v = texture2D(sampler_v,v_texPo).r- 0.5;
    vec3 rgb;
    rgb.r = y + 1.403 * v;
    rgb.g = y - 0.344 * u - 0.714 * v;
    rgb.b = y + 1.770 * u;

    gl_FragColor=vec4(rgb,1);
}

因为OpenGLES需要用rgb来加载显示,这里就需要将yuvrgb,这里放在OpenGL里面转换,OpenGL里面使用GPU,提高性能。

  1. 数据写入

YUV420Texture.java



import android.content.Context;
import android.opengl.GLES20;


import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

public class YUV420Texture {

    private Context context;

    //顶点坐标
    static float vertexData[] = {   // in counterclockwise order:
            -1f, -1f, 0.0f, // bottom left
            1f, -1f, 0.0f, // bottom right
            -1f, 1f, 0.0f, // top left
            1f, 1f, 0.0f,  // top right
    };

    //纹理坐标
    static float textureData[] = {   // in counterclockwise order:
            0f, 1f, 0.0f, // bottom left
            1f, 1f, 0.0f, // bottom right
            0f, 0f, 0.0f, // top left
            1f, 0f, 0.0f,  // top right
    };

    //每一次取点的时候取几个点
    static final int COORDS_PER_VERTEX = 3;

    private final int vertexCount = vertexData.length / COORDS_PER_VERTEX;
    //每一次取的总的点 大小
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

    //位置
    private FloatBuffer vertexBuffer;
    //纹理
    private FloatBuffer textureBuffer;

    private int program;

    //顶点位置
    private int avPosition;
    //纹理位置
    private int afPosition;

    //shader  yuv变量
    private int sampler_y;
    private int sampler_u;
    private int sampler_v;
    private int[] textureId_yuv;


    //YUV数据
    private int width_yuv;
    private int height_yuv;
    private ByteBuffer y;
    private ByteBuffer u;
    private ByteBuffer v;


    public YUV420Texture(Context context) {
        this.context = context;

        vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(vertexData);
        vertexBuffer.position(0);

        textureBuffer = ByteBuffer.allocateDirect(textureData.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(textureData);
        textureBuffer.position(0);
    }

    public void initYUV() {
        String vertexSource = ShaderUtil.readRawTxt(context, R.raw.vertex_shader);
        String fragmentSource = ShaderUtil.readRawTxt(context, R.raw.fragment_shader);
        program = ShaderUtil.createProgram(vertexSource, fragmentSource);
        if (program > 0) {
            //获取顶点坐标字段
            avPosition = GLES20.glGetAttribLocation(program, "av_Position");
            //获取纹理坐标字段
            afPosition = GLES20.glGetAttribLocation(program, "af_Position");
            //获取yuv字段
            sampler_y = GLES20.glGetUniformLocation(program, "sampler_y");
            sampler_u = GLES20.glGetUniformLocation(program, "sampler_u");
            sampler_v = GLES20.glGetUniformLocation(program, "sampler_v");

            textureId_yuv = new int[3];
            //创建3个纹理
            GLES20.glGenTextures(3, textureId_yuv, 0);

            //绑定纹理
            for (int id : textureId_yuv) {
                GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id);
                //环绕(超出纹理坐标范围)  (s==x t==y GL_REPEAT 重复)
                GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
                GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
                //过滤(纹理像素映射到坐标点)  (缩小、放大:GL_LINEAR线性)
                GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
                GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
            }

        }

    }

    public void setYUVData(int width, int height, byte[] y, byte[] u, byte[] v) {
        this.width_yuv = width;
        this.height_yuv = height;
        this.y = ByteBuffer.wrap(y);
        this.u = ByteBuffer.wrap(u);
        this.v = ByteBuffer.wrap(v);
    }

    public void draw() {
        if (width_yuv > 0 && height_yuv > 0 && y != null && u != null && v != null) {
            GLES20.glUseProgram(program);
            GLES20.glEnableVertexAttribArray(avPosition);
            GLES20.glVertexAttribPointer(avPosition, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);

            GLES20.glEnableVertexAttribArray(afPosition);
            GLES20.glVertexAttribPointer(afPosition, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, textureBuffer);

            //激活纹理0来绑定y数据
            GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId_yuv[0]);
            //glTexImage2D (int target,
            //                int level,
            //                int internalformat,
            //                int width,
            //                int height,
            //                int border,
            //                int format,
            //                int type,
            //                Buffer pixels)
            GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, width_yuv, height_yuv, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, y);

            //激活纹理1来绑定u数据
            GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId_yuv[1]);
            GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, width_yuv / 2, height_yuv / 2, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, u);

            //激活纹理2来绑定u数据
            GLES20.glActiveTexture(GLES20.GL_TEXTURE2);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId_yuv[2]);
            GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, width_yuv / 2, height_yuv / 2, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, v);

            //给fragment_shader里面yuv变量设置值   0 1 2 标识纹理x
            GLES20.glUniform1i(sampler_y, 0);
            GLES20.glUniform1i(sampler_u, 1);
            GLES20.glUniform1i(sampler_v, 2);

            //绘制
            GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vertexCount);

            y.clear();
            u.clear();
            v.clear();
            y = null;
            u = null;
            v = null;
            GLES20.glDisableVertexAttribArray(afPosition);
            GLES20.glDisableVertexAttribArray(avPosition);

        }
    }
}


ShaderUtil.java


import android.content.Context;
import android.opengl.GLES20;
import android.util.Log;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

public class ShaderUtil {
    private static final String TAG = "ShaderUtil";


    public static String readRawTxt(Context context, int rawId) {
        InputStream inputStream = context.getResources().openRawResource(rawId);
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuffer sb = new StringBuffer();
        String line;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line).append("\n");
            }
            reader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

    public static int loadShader(int shaderType, String source) {
        // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
        // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
        int shader = GLES20.glCreateShader(shaderType);
        if (shader != 0) {
            //添加代码到shader
            GLES20.glShaderSource(shader, source);
            //编译shader
            GLES20.glCompileShader(shader);
            int[] compile = new int[1];
            //检测是否编译成功
            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compile, 0);
            if (compile[0] != GLES20.GL_TRUE) {
                Log.d(TAG, "shader compile error");
                GLES20.glDeleteShader(shader);
                shader = 0;
            }
        }
        return shader;
    }

    public static int createProgram(String vertexSource, String fragmentSource) {
        //获取vertex shader
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
        if (vertexShader == 0) {
            return 0;
        }
        //获取fragment shader
        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
        if (fragmentShader == 0) {
            return 0;
        }
        //创建一个空的渲染程序
        int program = GLES20.glCreateProgram();
        if (program != 0) {
            //添加vertexShader到渲染程序
            GLES20.glAttachShader(program, vertexShader);
            //添加fragmentShader到渲染程序
            GLES20.glAttachShader(program, fragmentShader);
            //关联为可执行渲染程序
            GLES20.glLinkProgram(program);
            int[] linsStatus = new int[1];
            //检测是否关联成功
            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linsStatus, 0);
            if (linsStatus[0] != GLES20.GL_TRUE) {
                Log.d(TAG, "link program error");
                GLES20.glDeleteProgram(program);
                program = 0;
            }
        }
        return program;

    }

}

  1. Render书写
    MyRender.java
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class MyRender implements GLSurfaceView.Renderer {

    private Context context;

    private YUV420Texture yuv420Texture;

    public MyRender(Context context) {
        this.context = context;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        yuv420Texture = new YUV420Texture(context);
        yuv420Texture.initYUV();
    }


    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //宽高
        GLES20.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //清空颜色
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        //设置背景颜色
//        GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);

        yuv420Texture.draw();
    }

    public void setYuvData(int width, int height, byte[] y, byte[] u, byte[] v) {
        if (yuv420Texture != null) {
            yuv420Texture.setYUVData(width, height, y, u, v);
        }
    }
}

  1. GLSurfaceView引用Renderer

MyGLSurfaceView.java


import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;

public class MyGLSurfaceView extends GLSurfaceView {

    private MyRender myRender;

    public MyGLSurfaceView(Context context) {
        this(context, null);
    }

    public MyGLSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setEGLContextClientVersion(2);
        myRender = new MyRender(context);
        setRenderer(myRender);
        //mode=GLSurfaceView.RENDERMODE_WHEN_DIRTY之后  调用requestRender()触发Render的onDrawFrame函数
        //mode=GLSurfaceView.RENDERMODE_CONTINUOUSLY之后  自动调用onDrawFrame  60fps左右
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }

    public void setYUVData(int width, int height, byte[] y, byte[] u, byte[] v) {
        if (myRender != null) {
            myRender.setYuvData(width, height, y, u, v);
            requestRender();
        }
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android OpenGLES绘制yuv420纹理 的相关文章

  • Android 应用程序在后台运行时保存数据

    目前我正在开发 xmmp 客户端 当应用程序位于前台时 该客户端工作得很好 但由于事实上 当应用程序处于后台时 我在 Application 类中保存了大量数据 复杂的 ArrayList 字符串和布尔值作为公共静态 每个字段都被垃圾收集
  • 为什么将函数参数声明为最终的?

    我目前正在阅读 Sams 出版的 24 小时自学 Android 应用程序开发 一书 我对 Java Android 或其他方面还比较陌生 我对 ActionScript 3 有非常扎实的背景 它与 Java 有足够的相似之处 因此该语言本
  • 如何在 cv2.VideoWriter 中使用 FPS 参数?

    好的 所以我正在制作视频 我想确切地知道如何使用 FPS 参数 它是一个浮点数 所以我假设这是我想要的每帧之间的间隔 你能给个例子吗 我只想知道视频会如何随着 FPS 参数值的变化而变化 因为我制作的视频现在太快了 谢谢 确实只是这样 fr
  • Phonegap - 如何将.txt文件保存在Android手机的根目录中

    我正在尝试使用phonegap 将 txt 文件保存在Android 手机的根目录中 我已经安装了这些插件 cordova plugin file 和 cordova plugin file transfer 在 config xml 文件
  • RxJava、Proguard 和 sun.misc.Unsafe

    我有以下问题RxJava 1 1 0 使用时Proguard 我没有更改 RxJava 版本或其 pro文件 但更新后OkHttp我无法编译使用Proguard因为我有关于sun misc Unsafe不在场 rxJava pro keep
  • 已经使用 AsyncTask doInBackground 但新数据未显示

    我使用 AsyncTask 创建一个聊天室来接收消息 因此它总是检查即将到来的消息并将其显示给客户端 但代码似乎无法按我希望的方式工作 在客户端只显示所有旧数据 新数据不显示 因为当我尝试从服务器发送消息时 新数据没有显示在客户端中 我对这
  • 在我的Android中,当其他应用程序想要录制音频时如何停止录音?

    在我的应用程序中 服务通过 AudioRecord 持续录制音频 当我的应用程序运行时 其他与音频记录相关的应用程序 例如 Google 搜索 无法工作 如何知道何时有其他应用想要录制音频 以便我可以停止录制以释放资源 答案是MediaRe
  • 请求位置更新参数

    这就是 requestLocationUpdates 的样子 我使用它的方式 requestLocationUpdates String provider long minTime float minDistance LocationLis
  • minHeight 有什么作用吗?

    在附图中 我希望按钮列与图像的高度相匹配 但我也希望按钮列有一个最小高度 它正确匹配图像的高度 但不遵守 minHeight 并且会使按钮向下滑动 我正在为按钮列设置这些属性
  • 带有自定义阵列适配器的微调器不允许选择项目

    我使用自定义阵列适配器作为微调器 但是 当在下拉列表中选择一个项目时 下拉列表保留在那里 并且微调器不会更新 这是错误行为 与使用带有字符串的通用数组适配器相比 这是自定义类 我错过了什么吗 谢谢 public class Calendar
  • Android相机意图:如何获取全尺寸照片?

    我正在使用意图来启动相机 Intent cameraIntent new Intent android provider MediaStore ACTION IMAGE CAPTURE getParent startActivityForR
  • Android Studio:无法启动守护进程

    当我尝试在 Android Studio 中导入 gradle 项目时 遇到以下错误 Unable to start the daemon process This problem might be caused by incorrect
  • 如何在不更改手机语言的情况下更改Android应用程序语言?

    我希望用户在应用程序内选择一种语言 选择语言后 我希望字符串使用特定语言 如果我更改手机语言 那么我的应用程序将以设置的语言运行 我无法找到任何在不更改手机语言的情况下设置语言的方法 此外 一旦设置了语言 更改就应该反映出来 有人可以建议一
  • Android Webview 图像未加载

    我制作了一个简单的应用程序WebView 但有些图片无法加载 正确 在我的电脑上 错误 在模拟器中 Correct 错误 没有横幅 于是我用Chrome debug进行调试 发现我的代码被改变了 我不添加像noscript or style
  • 在 Android 上按下电源按钮时,如何防止先调用 onDestroy() 再调用 onCreate()

    我正在记录每个 onCreate 和 onDestroy 调用 我发现 一旦我单击 Android 上的电源按钮 以及模拟器上的电源按钮 我的活动中就会拨打电话 gt onDestroy gt onCreate 这会杀死我的游戏 然后立即从
  • Android - 将 ImageView 保存到具有全分辨率图像的文件

    我将图像放入 ImageView 中 并实现了多点触控来调整 ImageView 中的图像大小和移动图像 现在我需要将调整大小的图像保存到图像文件中 我已经尝试过 getDrawingCache 但该图像具有 ImageView 的大小 我
  • Android 如何聚焦当前位置

    您好 我有一个 Android 应用程序 可以在谷歌地图上找到您的位置 但是当我启动该应用程序时 它从非洲开始 而不是在我当前的城市 国家 位置等 我已经在developer android com上检查了信息与位置问题有关 但问题仍然存在
  • 用于推送通知的设备令牌

    我正在实施推送通知服务 我需要创建一个数据库来存储 4 个移动平台的所有设备令牌 我想根据他们的平台 iOS Android BlackBerry WP7 来组织它们 但是有什么方法可以区分平台 这样如果我只想向 Android 用户发送消
  • android Accessibility-service 突然停止触发事件

    我有一个 AccessibilityService 工作正常 但由于开发过程中的某些原因它停止工作 我似乎找不到这个原因 请看一下我的代码并告诉我为什么它不起作用 public class MyServicee extends Access
  • 无法运行我的应用程序,要求选择 Android SDK

    今天我已经安装了Android Studio 金丝雀 1 现在我无法运行我的应用程序 将出现以下对话框 我已经通过 文件 gt 项目结构 gt Android SDK 位置 设置了正确的 SDK 位置 期待您的帮助来解决这个问题 警告对话框

随机推荐

  • 微服务设计指导-hystrix参数介绍

    连接nacos的配置 nacos 服务地址 spring cloud nacos discovery server addr nacos server addr 注册到nacos上的命名空间 spring cloud nacos disco
  • MySQL数据库基础知识11,查询缓存

    目录 一 查询缓存是什么 二 MySQL如何判断缓存命中 三 使用查询缓存需谨慎 四 如何分析和配置查询缓存 五 InnoDB和查询缓存 MySQL进阶实战系列文章 哪吒精品系列文章 一 查询缓存是什么 MySQL查询缓存保存查询返回的完整
  • 【OpenCV图像处理】1.23 直方图(Histogram)均衡化

    文章目录 相关理论 代码 展示效果 相关理论 什么是直方图 图像直方图 是指对整个图像像在灰度范围内的像素值 0 255 统计出现频率次数 据此生成的直方图 称为图像直方图 直方图 直方图反映了图像灰度的分布情况 是图像的统计学特征 直方图
  • 逆水寒紫禁之巅服务器维护,逆水寒"紫禁之巅"服务器或将成历史?王思聪、PDD时代已渐行渐远...

    原标题 逆水寒 紫禁之巅 服务器或将成历史 王思聪 PDD时代已渐行渐远 从没有一款端游 能像 逆水寒 这样 能吸引如此多的社会名流入驻游戏 在游戏开测之初 打着 五年五亿巨制 网易最后一款端游 的口号 连王思聪 秦奋等名人都纷纷入坑 逆水
  • 龙书笔记(9)

    chap 9 字体 生成和输出文本的3种方式 ID3DXFont CD3DFont D3DXCreateText 1 ID3DXFont接口 能处理一些复杂的字体和格式 但是速度略慢 创建ID3DXFont对象 D3DXFONT DESC
  • 爬虫:爬取Github项目结构、任意文件下载存储

    文章目录 场景描述 爬取 Github 项目的文件结构 爬取 Laravel 8 x 文件结构 编写脚本 访问 Github 连接超时 requests 读取时间超时 爬取脚本 任意文件下载脚本 场景描述 需求 发现 任意文件下载漏洞 后
  • C++ 结构体(struct)的继承

    C 中的struct对C中的struct进行了扩充 它已经不再只是一个包含不同数据类型的数据结构了 它已经获取了太多的功能 struct能包含成员函数吗 能 struct能继承吗 能 struct能实现多态吗 能 有很多人应该已经知道这样一
  • C++ 保留2位小数

    include
  • 索尼 toio™ 应用创意开发征文|小巧机器,大无限,探索奇妙世界

    文章目录 前言 微型机器人的未来 toio 小机器人简介 toio 小机器人 创新功能一览 toio 小机器人 多领域的变革者 toio 小机器人贪吃蛇游戏 代码实现 写在最后 前言 当我们谈到现代科技的创新时 往往会联想到复杂的机器和高级
  • VS2015新建项目时,左侧面板空白的解决方法

    VS2015新建项目时 左侧面板空白的解决方法 解决办法是 1 进入 C Users 当前用户名 一般为administrator AppData Local Microsoft VisualStudio 14 0 2 删除或重命名 Com
  • HDU 1007 Quoit Design竟然要分治

    Quoit Design Time Limit 10000 5000 MS Java Others Memory Limit 65536 32768 K Java Others Total Submission s 34742 Accept
  • Class 08 - 数据的读取和保存 & R语言中的管道(pip)功能

    Class 08 数据的读取和保存 R语言中的管道 pip 功能 数据的读取和保存 data 加载R中的数据集 readr 功能包介绍 readr 包中读取文件的函数 read csv 读取 csv 文件 readxl 包读取Excel文件
  • 小兔鲜儿 - 推荐模块

    目录 动态获取数据 静态结构 获取页面参数 获取数据 类型声明 热门推荐 渲染页面和Tab交互 热门推荐 分页加载 热门推荐 分页条件 type 和 interface 的区别 type 和 interface 的相似之处 type 的特点
  • C++ 静态库和动态库的区别

    库是C 中的函数集合 用于存放共享代码的 C 的库分为静态库和动态库 动态库将函数的声明和实现分开成两部分 分别存放在了两个文件中 而C 的函数声明就存放在了 lib 文件中 如果是静态库的话 lib 文件还会存放函数的代码本身和函数的实现
  • 基于 Jmeter 的轻量级云压测平台的原理与实现 :压测引擎

    目录 前言 平台的技术 平台的初衷 平台从开源开始到现在拥有了一些核心的功能 印象深刻的技术点 为什么执着于 Jmeter API 平台能带来什么 压测引擎 前端入口 Controller 必要的 Jmeter 配置准备 对 Jmeter
  • vue+file-saver+xlsx 封装导出Excel表格方法

    file saver xlsx 封装通用导出方法 安装插件依赖 npm i xlsx 0 17 0 npm i file saver 2 0 5 2 在utils文件夹中创建ExportExcel js文件 eslint disable i
  • write写文件乱码

    include
  • Python将图片转换为灰度图

    很多时候我们需要用到灰度图像 比如说在深度学习的训练中 有时候我们并不需要图片的颜色信息 然而我们日常环境下获得的通常都是彩色图像 所以需要将彩色图像转换成灰度图像 也就是从3个通道 RGB 转换成一个通道 from PIL import
  • 学会项目成本管理计算,PMP计算题就是送分题

    学会项目成本管理计算 PMP计算题就是送分题 PMP中的计算主要在 lt 项目成本管理 gt 的控制成本部分 服务于挣值管理 EVM Earned Value Management 挣值分析 EVA Earned Value Analysi
  • Android OpenGLES绘制yuv420纹理

    Android OpenGLES绘制yuv420纹理 曾大稳丶 关注 2018 07 16 11 31 字数 76 阅读 440评论 0喜欢 3 把shader代码写入raw里面 vertex shader glsl attribute v