美颜相机 MagicCamera项目的学习及拍照黑屏,拉伸问题的处理

最近公司需要开发一套美颜相机功能,查阅相关资料得到结论,美颜滤镜归根揭底是opengl es 加GPUImage实时滤镜渲染。

这块经验不足,所以就取开源库寻找项目,最终找到了一个4000+星的MagicCamera项目。这个项目还是非常有参考价值的,找了多个项目对比后发现思路都差不多,有很多项目都能看到MagicCamera的影子,听说有的已经在大规模使用的相机软件都是在这个项目的基础上研发的。自然就要down下来学习一下了。

当然目前MagicCamera项目太老了直接是跑不起来的需要自己配置CMake编译后才能正常跑起来,magicCameraNew这个已经配置好CMake编译了,只要本地配置好编译环境就可以跑起来了。这个项目目前只有相机实时预览加拍照和录制视频。没有提供照片编辑入口,需要自己使用MagicImageView就可以完成编辑功能

实时美颜和滤镜效果没有问题,但是在拍照后生成的效果没有美颜和滤镜的效果,github上也很多人反馈拍照黑屏,这里我解决了

黑屏的问题。

先来看下拍照的流程及代码

  CameraEngine.takePicture(null, null, new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                CameraEngine.stopPreview();
                final Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                //要利用OpenGl 绘图暂时停止摄像头预览,并且渲染操作要放到EGLContenxt环境下
                queueEvent(new Runnable() {
                    @Override
                    public void run() {
                        //把摄像头获取的bitmap图片经过Opengl滤镜处理后返回保存
                        final Bitmap photo = drawPhoto(bitmap,CameraEngine.getCameraInfo().isFront);
                        GLES20.glViewport(0, 0, surfaceWidth, surfaceHeight);
                        if (photo != null)
                            savePictureTask.execute(photo);
                    }
                });
                CameraEngine.startPreview();
            }
        });

这里拍照流程没有什么问题,摄像头获取的bitmap也是正常的数据,这里顺便提一下摄像头预览和拍照的尺寸往往有多个Size需要根据需求选择最优的,一般图片拉伸都是Size设置和保存时的Size不一致导致的。Size设置代码如下

private static void setDefaultParameters(){
        Parameters parameters = camera.getParameters();
        if (parameters.getSupportedFocusModes().contains(
                Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
            parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
        }
        Size previewSize = CameraUtils.getLargePreviewSize(camera);
        Size pictureSize = CameraUtils.getLargePictureSize(camera);
        //设置预览尺寸
        parameters.setPreviewSize(previewSize.width, previewSize.height);
        //设置拍照尺寸
        parameters.setPictureSize(pictureSize.width, pictureSize.height);
        parameters.setRotation(90);
        camera.setParameters(parameters);
    }

这样处理后只要保证了拍照比例和处理图片时的Size比例一样就不会拉伸了。

 

接下来看看原始drawPhoto里干了些啥

private Bitmap drawPhoto(Bitmap bitmap,boolean isRotated){
        //设置最终生成的图片的宽高
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        
        if(beautyFilter == null)
            beautyFilter = new MagicBeautyFilter();
        beautyFilter.init();
        beautyFilter.onDisplaySizeChanged(width, height);
        beautyFilter.onInputSizeChanged(width, height);

        if(filter != null) {
            filter.onInputSizeChanged(width, height);
            filter.onDisplaySizeChanged(width, height);
        }

        
        //创建并使用FBO
        int[] mFrameBuffers = new int[1];
        int[] mFrameBufferTextures = new int[1];
        GLES20.glGenFramebuffers(1, mFrameBuffers, 0);
        GLES20.glGenTextures(1, mFrameBufferTextures, 0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mFrameBufferTextures[0]);
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0,
                GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[0]);
        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
                GLES20.GL_TEXTURE_2D, mFrameBufferTextures[0], 0);

        //设置渲染窗口大小
        GLES20.glViewport(0, 0, width, height);
        //加载原始图片到纹理对象中
        int textureId = OpenGlUtils.loadTexture(bitmap, OpenGlUtils.NO_TEXTURE, true);

        FloatBuffer gLCubeBuffer = ByteBuffer.allocateDirect(TextureRotationUtil.CUBE.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        FloatBuffer gLTextureBuffer = ByteBuffer.allocateDirect(TextureRotationUtil.TEXTURE_NO_ROTATION.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        gLCubeBuffer.put(TextureRotationUtil.CUBE).position(0);
        //这是纹理是否要旋转,因为前置摄像头拍照时反转了的
        if(isRotated)
            gLTextureBuffer.put(TextureRotationUtil.getRotation(Rotation.NORMAL, false, false)).position(0);
        else
            gLTextureBuffer.put(TextureRotationUtil.getRotation(Rotation.NORMAL, false, true)).position(0);


        if(filter == null){
            //只要美颜滤镜直接渲染
            beautyFilter.onDrawFrame(textureId, gLCubeBuffer, gLTextureBuffer);
        }else{
            //双滤镜叠加,首先美颜渲染到原始图片纹理上
            beautyFilter.onDrawFrame(textureId);
            //然后颜色滤镜渲染到帧缓存FBO上
            filter.onDrawFrame(mFrameBufferTextures[0], gLCubeBuffer, gLTextureBuffer);
        }
        
        //从EGL环境下获取图片数据
        IntBuffer ib = IntBuffer.allocate(width * height);
        GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, ib);
        Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        result.copyPixelsFromBuffer(ib);

        //释放资源
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
        GLES20.glDeleteTextures(1, new int[]{textureId}, 0);
        GLES20.glDeleteFramebuffers(mFrameBuffers.length, mFrameBuffers, 0);
        GLES20.glDeleteTextures(mFrameBufferTextures.length, mFrameBufferTextures, 0);

        beautyFilter.destroy();
        beautyFilter = null;
        if(filter != null) {
            filter.onDisplaySizeChanged(surfaceWidth, surfaceHeight);
            filter.onInputSizeChanged(imageWidth, imageHeight);
        }
        return result;
    }

经过这段代码处理后就能获取到渲染后的图片,如果只有美颜效果时没有问题的,但是如果有filter时拍照获取的图片就是黑的

这个时候我看了下FBO的使用滤镜渲染应该要修改为如下代码

   if(filter == null){
            //只要美颜滤镜直接渲染
            beautyFilter.onDrawFrame(textureId, gLCubeBuffer, gLTextureBuffer);
        }else{
            //双滤镜叠加,首先美颜渲染到原始图片纹理上
            beautyFilter.onDrawFrame(textureId);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);
            GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
            //然后颜色滤镜渲染到帧缓存FBO上
            filter.onDrawFrame(mFrameBufferTextures[0], gLCubeBuffer, gLTextureBuffer);
        }

这个时候组合滤镜生成的图片如下4304x5760,可见区域的图像是

 

[图片]

初步怀疑时viewport设置的问题,但是在尝试后发现并没有什么效果,分析图片尺寸后发现原来时图片大小和Opengl es渲染的图像的区域不一致OpenglES 渲染的区域由SurfaceView决定(有问题可以交流)。这里就不让渲染原图大小了,宽高根据预览尺寸来设置就没问题了。代码如下

   //设置最终生成的图片的宽高
        CameraInfo info = CameraEngine.getCameraInfo();
        int width = info.previewHeight;
        int height = info.previewWidth;

这样就双滤镜叠加效果就可以正常使用了。其实多滤镜效果也是使用FBO去渲染,流程和原理类似,可以参考

MagicBaseGroupFilter里的代码。

本人是小白,如有不正确的地方,还希望各位兄弟及时提出不吝赐教。

下一篇会介绍不采用离屏渲染bitmap拍照的方式,而是之前在当前渲染的环境下截图。

 

修改后的动态源码参考github

【资源说明】 基于PYQT5和图像处理算法的智能美颜相机python源码+项目说明.zip dlib人脸68点特征点检测模型官网下载地址:http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2 # 本文基于PYQT5和图像处理算法,实现滤镜、增强、美颜、涂鸦、贴纸、文字和马赛克等丰富功能。 # 具体功能如下: 1) 首先利用人脸识别模型检测输入图像中的人脸位置。借助dlib人脸关键特征点,应用程序能够准确地定位人脸的关键部位,如眼睛、嘴巴等。 2) 滤镜功能使用户能够通过应用各种滤镜效果改变图像的外观。用户可以选择不同的滤镜样式,如怀旧、铅笔画、哈哈镜等,以实现不同的艺术效果。 3) 增强功能提供了调整图像属性的选项。用户可以增加亮度、对比度、饱和度等,以改善图像的质量和视觉效果。 4) 美颜功能是该应用程序的亮点之一。通过应用各种图像处理技术,如美白、磨皮、瘦脸和大眼。 5) 涂鸦功能为用户提供了自由绘制的能力。他们可以在图像上绘制椭圆、矩形等,为照片增添创意和个性。 6) 贴纸功能允许用户添加各种贴纸或装饰物到图像上。用户可以选择喜欢的贴纸,如比心、飞机等,以增加图像的趣味性和个性化。 7) 文字功能允许用户在图像上添加自定义文本。他们可以添加标题、标签、注释或个性化的文字消息,以传达特定的信息或增强照片的表达力。 8) 马赛克功能可以用于模糊图像。用户可以选择马赛克效果来隐藏敏感信息或保护隐私。 此外,美颜相机还支持摄像头拍照功能,这提供了便捷的方式来捕捉美丽的瞬间,为后面的美颜操作提供便利。同时,美颜相机提供了图片缩放、撤销还原等实用功能,使用户能够调整图像的大小和恢复到之前的状态。核心功能如图所示。 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载使用,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值