首页 文章 精选 留言 我的

精选列表

搜索[视觉驱动],共10000篇文章
优秀的个人博客,低调大师

[OpenGL]未来视觉2-Android摄像头帧采集

这节相机的渲染的介绍,只涉及到二维平面的渲染,所以不需要关注三维变量。 先看一下总图 OpenGL采集总图.png 下面是相机采集初始化处理 JNIEXPORT jint JNICALL Java_com_cangwang_magic_util_OpenGLJniLib_magicBaseInit(JNIEnv *env, jobject obj, jobject surface,jint width,jint height,jobject assetManager) { std::unique_lock<std::mutex> lock(gMutex); if(glCamera){ glCamera->stop(); delete glCamera; } //创建window ANativeWindow *window = ANativeWindow_fromSurface(env,surface); //获取文件管理 AAssetManager *manager = AAssetManager_fromJava(env,assetManager); //初始化相机引擎 glCamera = new CameraEngine(window); glCamera->setAssetManager(manager); glCamera->resize(width,height); //创建相机采集 return glCamera->create(); } 需要先确定好相机的变换矩阵,这个初始化并不重要。 { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1} CameraEngine::CameraEngine(ANativeWindow *window): mWindow(window),mEGLCore(new EGLCore()), mAssetManager(nullptr),mTextureId(0),mTextureLoc(0), mMatrixLoc(0){ //清空mMatrix数组 memset(mMatrix,0, sizeof(mMatrix)); mMatrix[0] = 1; mMatrix[5] = 1; mMatrix[10] = 1; mMatrix[15] = 1; } 初始化采集相机纹理的参数。这里需要了解,采集相机是需要GL_TEXTURE_EXTERNAL_OES来绑定相机纹理,因为相机数据是YUV的编码格式,而显示到屏幕上应该是RGB或者RGBA格式,那么就需要转换,如果我们采集后再自己手动转换,将非常耗费GPU和CPU资源,Opengl提供了更加底层的采集解析才能平稳的将数据转化,这里就需要引用到GLES2/gl2ext.h,这是独有的扩展纹理库。 int CameraEngine::create() { //这里面需要初始化EGL if (!mEGLCore->buildContext(mWindow)){ return -1; } //读取顶点着色器 std::string *vShader = readShaderFromAsset(mAssetManager,"camera.vert"); //读取片段着色器 std::string *fShader = readShaderFromAsset(mAssetManager,"camera.frag"); //加载程序 mProgram = loadProgram(vShader->c_str(),fShader->c_str()); //生成纹理贴图 glGenTextures(1,&mTextureId); //绑定纹理,这里面使用GL_TEXTURE_EXTERNAL_OES用于采集相机纹理 glBindTexture(GL_TEXTURE_EXTERNAL_OES,mTextureId); //设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色,少量计算,渲染比较快,但是效果差 glTexParameterf(GL_TEXTURE_EXTERNAL_OES,GL_TEXTURE_MIN_FILTER,GL_NEAREST); //设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色,需要算法计算,用时相对变长,效果好 glTexParameterf(GL_TEXTURE_EXTERNAL_OES,GL_TEXTURE_MAG_FILTER,GL_LINEAR); //这里GL_TEXTURE_WRAP_S 纹理坐标是以S轴方向与T轴方向纹理(对应平面坐标x,y方向) glTexParameterf(GL_TEXTURE_EXTERNAL_OES,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_EXTERNAL_OES,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE); //初始化矩阵绑定 mMatrixLoc = glGetUniformLocation(mProgram,"mMatrix"); //初始化纹理绑定 mTextureLoc = glGetUniformLocation(mProgram,"sTexture"); //使用白色清屏 glClearColor(1.0f,1.0f,1.0f,1.0f); delete vShader; delete fShader; return mTextureId; } EGL是介于诸如OpenGL 或OpenVG的Khronos渲染API与底层本地平台窗口系统的接口。它被用于处理图形管理、表面/缓冲捆绑、渲染同步及支援使用其他Khronos API进行的高效、加速、混合模式2D和3D渲染。这里用于做离屏渲染(缓冲渲染)。介绍一下EGL初始化过程 GLboolean EGLCore::buildContext(ANativeWindow *window) { //与本地窗口通信 mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (mDisplay == EGL_NO_DISPLAY){ ALOGE("eglGetDisplay failed: %d",eglGetError()); return GL_FALSE; } GLint majorVersion; GLint minorVersion; //获取支持最低和最高版本 if (!eglInitialize(mDisplay,&majorVersion,&minorVersion)){ ALOGE("eglInitialize failed: %d",eglGetError()); return GL_FALSE; } EGLConfig config; EGLint numConfigs = 0; //颜色使用565,读写类型需要egl扩展 EGLint attribList[] = { EGL_RED_SIZE,5, //指定RGB中的R大小(bits) EGL_GREEN_SIZE,6, //指定G大小 EGL_BLUE_SIZE,5, //指定B大小 EGL_RENDERABLE_TYPE,EGL_OPENGL_ES3_BIT_KHR, //渲染类型,为相机扩展类型 EGL_SURFACE_TYPE,EGL_WINDOW_BIT, //绘图类型, EGL_NONE }; //让EGL推荐匹配的EGLConfig if(!eglChooseConfig(mDisplay,attribList,&config,1,&numConfigs)){ ALOGE("eglChooseConfig failed: %d",eglGetError()); return GL_FALSE; } //找不到匹配的 if (numConfigs <1){ ALOGE("eglChooseConfig get config number less than one"); return GL_FALSE; } //创建渲染上下文 //只使用opengles3 GLint contextAttrib[] = {EGL_CONTEXT_CLIENT_VERSION,3,EGL_NONE}; // EGL_NO_CONTEXT表示不向其它的context共享资源 mContext = eglCreateContext(mDisplay,config,EGL_NO_CONTEXT,contextAttrib); if (mContext == EGL_NO_CONTEXT){ ALOGE("eglCreateContext failed: %d",eglGetError()); return GL_FALSE; } EGLint format = 0; if (!eglGetConfigAttrib(mDisplay,config,EGL_NATIVE_VISUAL_ID,&format)){ ALOGE("eglGetConfigAttrib failed: %d",eglGetError()); return GL_FALSE; } ANativeWindow_setBuffersGeometry(window,0,0,format); //创建On-Screen 渲染区域 mSurface = eglCreateWindowSurface(mDisplay,config,window,0); if (mSurface == EGL_NO_SURFACE){ ALOGE("eglCreateWindowSurface failed: %d",eglGetError()); return GL_FALSE; } //把EGLContext和EGLSurface关联起来 if (!eglMakeCurrent(mDisplay,mSurface,mSurface,mContext)){ ALOGE("eglMakeCurrent failed: %d",eglGetError()); return GL_FALSE; } ALOGD("buildContext Succeed"); return GL_TRUE; } 初始化之后,等待相机回调后,才可以开始开启绘制。 fun initOpenGL(surface: Surface, width: Int, height: Int){ mExecutor.execute { //获取纹理id val textureId = OpenGLJniLib.magicBaseInit(surface,width,height,BaseApplication.context.assets) if (textureId < 0){ //返回纹理绑定是否正常 Log.e(TAG, "surfaceCreated init OpenGL ES failed!") return@execute } //需要使用surfaceTexture来做纹理装载 mSurfaceTexture = SurfaceTexture(textureId) //添加纹理变化回调 mSurfaceTexture?.setOnFrameAvailableListener { //开始画图 drawOpenGL() } ……省略代码…… } } 这里需要读取到相机的变换矩阵纹理,矩阵中包括相机图片的图像是是否正对你,是否有角度偏移。我们都可以通过这个矩阵来调整 JNIEXPORT void JNICALL Java_com_cangwang_magic_util_OpenGLJniLib_magicBaseDraw(JNIEnv *env, jobject obj,jfloatArray matrix_) { //获取矩阵,数组转指针 jfloat *matrix = env->GetFloatArrayElements(matrix_,NULL); std::unique_lock<std::mutex> lock(gMutex); if (!glCamera){ ALOGE("draw error, glCamera is null"); return; } //启动画图 glCamera->draw(matrix); //释放矩阵内存 env->ReleaseFloatArrayElements(matrix_,matrix,0); } 使用GL_TEXTURE_EXTERNAL_OES纹理绘制,matrix是相机采集时传入的矩阵参数。 void CameraEngine::draw(GLfloat *matrix) { glViewport(0,0,mWidth,mHeight); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(mProgram); //激活纹理 glActiveTexture(GL_TEXTURE0); //绑定纹理 glBindTexture(GL_TEXTURE_EXTERNAL_OES,mTextureId); //加载纹理 glUniform1i(mTextureLoc,0); //加载矩阵 glUniformMatrix4fv(mMatrixLoc,1,GL_FALSE,matrix); //开启顶点数组缓冲区,第0个 glEnableVertexAttribArray(ATTRIB_POSITION); //参数1:顶点数组索引,参数2:每次取的数量 参数3:数据格式 参数4:是否需要浮点转换 参数5:跨距取值,参数6:保存顶点属性数据的缓冲区指针 glVertexAttribPointer(ATTRIB_POSITION,VERTEX_POS_SIZE,GL_FLOAT,GL_FALSE,0,VERTICES); //开启顶点数组缓冲区 第1个 glEnableVertexAttribArray(ATTRIB_TEXCOORD); glVertexAttribPointer(ATTRIB_TEXCOORD,TEX_COORD_POS_SZIE,GL_FLOAT,GL_FALSE,0,TEX_COORDS); //画方形 glDrawArrays(GL_TRIANGLE_STRIP,0,VERTEX_NUM); //关闭缓冲区 glDisableVertexAttribArray(ATTRIB_POSITION); //关闭缓冲区 glDisableVertexAttribArray(ATTRIB_TEXCOORD); //清空缓冲区,将指令送往硬件立刻执行 glFlush(); //缓冲区交换 mEGLCore->swapBuffer(); } 说一下离屏渲染之后,交换到前台显示的原理 void EGLCore::swapBuffer() { //双缓冲绘图,原来是检测出前台display和后台缓冲的差别的dirty区域,然后再区域替换buffer //1)首先计算非dirty区域,然后将非dirty区域数据从上一个buffer拷贝到当前buffer; //2)完成buffer内容的填充,然后将previousBuffer指向buffer,同时queue buffer。 //3)Dequeue一块新的buffer,并等待fence。如果等待超时,就将buffer cancel掉。 //4)按需重新计算buffer //5)Lock buffer,这样就实现page flip,也就是swapbuffer eglSwapBuffers(mDisplay,mSurface); } glsl分析.png 顶点着色器输入相机转换矩阵,来调整xy方向显示。 #version 300 es layout(location=0) in vec4 aPosition; layout(location=1) in vec4 aTexCoord; //相机转换矩阵 uniform mat4 mMatrix; out vec2 vTexCoord; void main() { //矩阵调整相机显示,相机像素的坐标 vTexCoord = (mMatrix * aTexCoord).xy; gl_Position = aPosition; } 这里需要注意的是片段着色器,需要声明使用opengl扩展库,才能使用samperExternalOES提示相机采集数据,并提示片段着色器采集之后转换。 #version 300 es #extension GL_OES_EGL_image_external_essl3 : require precision highp float; //相机采集纹理 uniform samplerExternalOES sTexture; //坐标 in vec2 vTexCoord; out vec4 fragColor; void main() { /texture/将与纹理坐标对应的纹理值从内存中取出来,像素坐标和像素纹理关联 //输出片段着色器的色值 fragColor = texture(sTexture, vTexCoord); } Native底层的相机图像采集介绍就到这,下一节会介绍,相机采帧转换介绍。

优秀的个人博客,低调大师

「OpenGL」未来视觉1-Android摄像头采集基础

相信很多人都用过相机功能,也开发过简单调度相机功能,但是相机采集功能。是图像信号输入的重要来源。 SurfaceView和View的不同之处: SurfaceView和View对比 相机图像采样,需要维持一个比较稳定的帧数来维持图像实时性,需要频繁刷新,创建一个子线程来进行画面更新,会被占用主线程效率好很多,而且双缓冲机制可以在画面从前台刷新到后台时才占用主线程操作,所以选用SurfaceView作绘制是最好的。 而GLSurfaceView是SurfaceView的一个子类,专用于openGL绘制,其运行效率远高于SurfaceView是因为使用了GPU参与绘制。 这一节介绍Android摄像头采样,还是采用了SurfaceView来做采样 1.需要申请相机权限。 <uses-permission android:name="android.permission.CAMERA" /> 2.打开摄像头,先检查摄像和前置摄像头,然后通过摄像头Id,来返回摄像头对象。 fun openCamera(cameraId:Int):Camera?{ if (!haveFeature(PackageManager.FEATURE_CAMERA)){ Log.e(TAG,"no camera!") return null } if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT && !haveFeature(PackageManager.FEATURE_CAMERA_FRONT)){ Log.e(TAG,"no front camera!") return null } val camera = Camera.open(cameraId) if (camera == null){ Log.e(TAG, "openCamera failed") return null } return camera } 3.设置画面比例 /** * 获取最大的图片大小 */ fun getLargePictureSize(camera: Camera?): Camera.Size? { if (camera != null) { //获取可选比例 val sizes = camera.parameters.supportedPictureSizes var temp: Camera.Size = sizes[0] for (i in 1 until sizes.size) { val scale = sizes[i].height.toFloat() / sizes[i].width if (temp.width < sizes[i].width && scale < 0.6f && scale > 0.5f) temp = sizes[i] } return temp } return null } /** * 获取最大的预览大小 */ fun getLargePreviewSize(camera: Camera?): Camera.Size? { if (camera != null) { //获取可选比例 val sizes = camera.parameters.supportedPreviewSizes var temp: Camera.Size = sizes[0] for (i in 1 until sizes.size) { if (temp.width < sizes[i].width) temp = sizes[i] } return temp } return null } /** * 相机采样参数大小 */ fun setOptimalSize(camera:Camera,aspectRatio:Float,maxWidth:Int,maxHeight:Int){ val parameters= camera.parameters //使用自动对焦 if (parameters.supportedFocusModes.contains( Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { parameters.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE } val size = getLargePreviewSize(camera) size?.let { //设置相机预览大小 parameters.setPreviewSize(it.width,it.height) Log.d(TAG, "input max: (" + maxWidth + ", " + maxHeight + "), output size: (" + it.width + ", " + it.height + ")") } val pictureSize = getLargePictureSize(camera) pictureSize?.let { //图片参数 parameters.setPictureSize(it.width,it.height) Log.d(TAG, "picture max: (" + maxWidth + ", " + maxHeight + "), output size: (" + it.width + ", " + it.height + ")") } camera.parameters = parameters } 3.设置相机图像角度 fun setDisplayOritation(activity: Activity, camera: Camera, cameraId: Int) { //获取window的角度 val rotation = activity.windowManager.defaultDisplay.rotation var degress = 0 when (rotation) { Surface.ROTATION_0 -> degress = 0 Surface.ROTATION_90 -> degress = 90 Surface.ROTATION_180 -> degress = 180 Surface.ROTATION_270 -> degress = 270 } val info = Camera.CameraInfo() Camera.getCameraInfo(cameraId, info) var result: Int //前置摄像头 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { result = (info.orientation + degress) % 360 result = (360 - result) % 360 // compensate the mirror } else {//后置摄像头 result = (info.orientation - degress + 360) % 360 // back-facing } Log.d(TAG, "window rotation: $degress, camera oritation: $result") camera.setDisplayOrientation(result) } 4.设置完摄像头参数后,需要设置一个SurfaceHolder.CallBack。 有三个必须的方法 //创建时调用 override fun surfaceCreated(holder: SurfaceHolder?) { } //当surface发生任何结构性的变化时(格式或者大小),该方法就会被立即调用。 override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { } //被移除时调用 override fun surfaceDestroyed(holder: SurfaceHolder?) {} 这里创建相机的调用surfaceCreated,之后会立刻调用一次surfaceChanged 这里在surfaceChanged上调用openGL的init操作 fun initOpenGL(surface: Surface, width: Int, height: Int){ //新开一个之后一个线程的线程池 mExecutor.execute { //获取纹理id val textureId = OpenGLJniLib.magicBaseInit(surface,width,height,BaseApplication.context.assets) if (textureId < 0){ Log.e(TAG, "surfaceCreated init OpenGL ES failed!") return@execute } //需要使用surfaceTexture来做纹理装载 mSurfaceTexture = SurfaceTexture(textureId) //添加纹理变化回调 mSurfaceTexture?.setOnFrameAvailableListener { drawOpenGL() } try { //把摄像头采样关联到纹理 mCamera?.setPreviewTexture(mSurfaceTexture) //开始摄像头采样 doStartPreview() }catch (e:IOException){ Log.e(TAG,e.localizedMessage) releaseOpenGL() } } } 5.开始预览,并开始自动对焦。 fun doStartPreview(){ mCamera?.startPreview() cameraFocus(width/2.0f,height/2.0f) } 6.opengl绘制,先要强制更新纹理图像,再更新获取纹理矩阵,然后让opengl绘制 fun drawOpenGL(){ mExecutor.execute { mSurfaceTexture?.updateTexImage() mSurfaceTexture?.getTransformMatrix(mMatrix) OpenGLJniLib.magicBaseDraw(mMatrix) } } 7.在destroySurfaceView的是否释放资源 fun releaseOpenGL(){ mExecutor.execute { mSurfaceTexture?.release() mSurfaceTexture=null OpenGLJniLib.magicBaseRelease() } } 介绍了Camera采样配置和,surfaceTexture纹理获取了和加载,下一节,将会介绍native层的opengl绘制代码。 Android摄像头采集基础介绍就到这。这里做一个小小推广。有需要Android系统技术进阶资料可加群;964557053 免费领取

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册