【Camera专题】HAL1- 多帧降噪算法的集成(实战1)

系列文章

动手入门第三方算法集成系列:
【Camera专题】HAL1- 实现第三方算法并集成到Android系统
【Camera专题】HAL1- 以SO库或a库的方式集成第三方算法
【Camera专题】HAL1- 多帧降噪算法的集成(实战1)

前面2篇文章,我们学习了集成算法的基本套路,也自己尝试写一个简单的算法进行集成。
本文将进入实战系列,学习和使用第三方算法公司的算法,集成到系统中。

知识点

  • 多帧图像的获取
  • 多帧降噪算法的集成
  • 一些调试技巧

多帧算法本身运行较慢,为了拍照更快,我选择在ZSL模式下集成。

一、多帧图像的获取(ZSL模式下获取5帧)

方式一

@@ -5223,6 +5308,7 @@ int QCamera2HardwareInterface::takePicture()
mActiveCameras, mMasterCamera);

if (mParameters.isZSLMode()) {
+ numSnapshots = 5;//设置拍照为5张
}
+++ b/hardware/qcom/camera/QCamera2/HAL/QCameraParameters.cpp
@@ -11421,6 +11421,7 @@ uint8_t QCameraParameters::getZSLQueueDepth()
if (isLowMemoryDevice()) {
qdepth = 1;
}
+ qdepth = 5;//设置队列深度为5
return (uint8_t)qdepth;
}

@@ -11442,6 +11443,7 @@ uint8_t QCameraParameters::getZSLBackLookCount()
if (isLowMemoryDevice()) {
look_back = 1;
}
+ look_back = 5;
return (uint8_t)look_back;
}

添加以上修改,在ZSL模式下拍照时,回调函数zsl_channel_cb就会调用5次,相应的就可以获取5帧数据。
这种方式是个高通请教的,但是不是很好的方式,
因为qdepth=5,缓存队列一开始就申请成5(默认是1),
进入预览的时候,内存直接就增大了。

方式二 (Burst-mode)

Zsl 分为两种mode:single shot;burst mode。
1.single shot:

预览之后,sensor 和VFE 会产生快照和预览帧,并且会把最新的一些帧保留在图像buffer中。一旦“取图”事件被触发,系统就会在第一时间内从图像buffer中把相关的图像找出并返回给用户,这就是ZSL,零秒延迟

2.Burst-mode
Burst mode 是single shot 特征的自然延伸。此功能允许用户捕获的不仅是当前帧,但也有几个帧之前和之后的当前帧的少数几个帧,从而捕捉到一个序列的图像到内存。这将为用户提供不同的快照时间,从中选择一个或多个帧来保存。应用了多少帧的选择自由是多少追溯帧和未来帧在记忆的局限性上,追溯和未来帧是相对于真正的快门时间的。

hardware/qcom/camera/QCamera2/HAL/QCameraParameters.cpp

int32_t QCameraParameters::setNumOfSnapshot(···) {
···
      if (isUbiRefocus()) {
          nBurstNum = m_pCapability->refocus_af_bracketing_need.output_count + 1;
      }
++      nExpnum = 5;//设置拍照张数为5张
      LOGD("mActiveCameras = %d, mbundledSnapshot = %d", mActiveCameras, mbundledSnapshot);
···
}

hardware/qcom/camera/QCamera2/HAL/QCamera2HWICallbacks.cpp

void * QCameraCbNotifier::cbNotifyRoutine(void * data) {
···
        case CAMERA_CMD_TYPE_START_DATA_PROC:
            {
                isSnapshotActive = TRUE;
                numOfSnapshotExpected = pme->mParent->numOfSnapshotsExpected();
                longShotEnabled = pme->mParent->isLongshotEnabled();
                numOfSnapshotExpected = 1;//设置期望返回给APP的照片数量为1
                LOGD("Num Snapshots Expected = %d",
                       numOfSnapshotExpected);
                numOfSnapshotRcvd = 0;
            }
···
}

packages/apps/SnapdragonCamera/src/com/android/camera/PhotoModule.java

public void onPictureTaken(byte [] jpegData, CameraProxy camera) {
 if(mSnapshotMode == CameraInfoWrapper.CAMERA_SUPPORT_MODE_ZSL) {
    Log.e(TAG, "zcf JpegPictureCallback : in zslmode");
    mParameters = mCameraDevice.getParameters();
    --- mBurstSnapNum = mParameters.getInt("num-snaps-per-shutter");
    +++ mBurstSnapNum = 1; //把数量改成1
    }
}

public boolean capture() {
        try {
            --- mBurstSnapNum = mParameters.getInt("num-snaps-per-shutter");
            +++ mBurstSnapNum = 1; //把数量改成1
        }catch (NumberFormatException ex){ 
            mBurstSnapNum = 1; 
        }
}

最后这里,需要把mBurstSnapNum数量设置成1,否则骁龙相机拍完照后,拍照按钮就没法继续响应拍照。
因为底层把拍照的数量设置成5了,APP要收到5张才算本次拍照完成,因此这里强行改成1。

这种方式,进入预览的时候,内存基本不变,只有拍照的瞬间,内存才会增加!

二、多帧降噪算法的集成(ZSL模式下获取5帧)

多帧降噪算法,就是把多帧图像合成一帧图像,减少噪点,应用场景:夜景拍照。

1.算法商提供的文件

【Camera专题】HAL1- 多帧降噪算法的集成(实战1)_第1张图片

1.学习doc,理解相关API和tuning
2.学习sample code,理解用法
调用流程为:

  • 初始化:MorphoLowlight_initialize(&pme->m_engine, &pme->m_parameters);
  • 设参数:MorphoLowlight_start(&pme->m_engine, &pme->m_out_img, &pme->m_parameters);
  • 添加照片:MorphoLowlight_addImage(&pme->m_engine,&input_image);
  • 算法处理:MorphoLowlight_process(&pme->m_engine,&base_index);
  • 释放资源:MorphoLowlight_finalize(&pme->m_engine);

2.算法集成

a.在Android源码中预编译算法库

  • 创建Morpho文件夹
    hardware/qcom/camera/QCamera2/Morpho


    【Camera专题】HAL1- 多帧降噪算法的集成(实战1)_第2张图片
  • 编写Android.mk文件
#默认不会加上lib前缀
LOCAL_MODULE := libmorpho_lowlight
#不管是release还是debug版本, 都编译这个模块
LOCAL_MODULE_TAGS := optional
#so文件路径以及名字 
LOCAL_SRC_FILES := lib/libmorpho_lowlight.so
LOCAL_MODULE_STEM := $(LOCAL_MODULE)
#需要指定文件后缀
LOCAL_MODULE_SUFFIX := $(suffix $(LOCAL_SRC_FILES))
#编译32位算法库
LOCAL_MULTILIB := 32
#表明预编译的是动态库
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
include $(BUILD_PREBUILT)

b.在调用的模块Android.mk中加入依赖库
hardware/qcom/camera/QCamera2/Android.mk

  LOCAL_CFLAGS += -DHAS_LOW_RAM
  endif
++# START MORPHO add by zcf
++ENABLE_MORPHO := true
++ifeq ($(ENABLE_MORPHO), true)
++#定义全局宏变量
++LOCAL_CFLAGS += -DMORPHO_LOW_LIGHT
++# 表明引入的头文件
++LOCAL_C_INCLUDES += $(LOCAL_PATH)/Morpho/include
++LOCAL_SHARED_LIBRARIES += libmorpho_lowlight
++endif
++#END MORPHO add by zcf
  
  LOCAL_STATIC_LIBRARIES := [email protected]

c.调用算法库
hardware/qcom/camera/QCamera2/HAL/QCamera2HWI.h

  • 导入头文件,定义相关的宏变量
++#ifdef MORPHO_LOW_LIGHT
++#include "morpho_lowlight_interface.h"
++#endif

class QCamera2HardwareInterface : public QCameraAllocator,
        public QCameraThermalCallback, public QCameraAdjustFPS
{
public:
···
      bool m_bOptimizeCacheOps;
++#ifdef MORPHO_LOW_LIGHT//zcf
++      int32_t m_iLLSShotNum;  //记录帧数
++      morpho_lowlight m_engine;//第三方算法引擎
++      morpho_lowlight_parameter m_parameters;//第三方算法参数
++      morpho_ImageDataEx m_out_img;////第三方算法输出
++#endif
      bool m_bNeedHalPP;
};
  • 初始化相关变量
    hardware/qcom/camera/QCamera2/HAL/QCamera2HWI.cpp
QCamera2HardwareInterface::QCamera2HardwareInterface(uint32_t cameraId)
:
···
++#ifdef MORPHO_LOW_LIGHT
++      m_iLLSShotNum(0),
++#endif
        m_bNeedHalPP(FALSE)
  {
  • 调用算法处理
    hardware/qcom/camera/QCamera2/HAL/QCamera2HWICallbacks.cpp
void QCamera2HardwareInterface::zsl_channel_cb(···) {
···
#ifdef MORPHO_LOW_LIGHT//zcf
    //add morpho_low_light begin
    QCameraStream *pLLSStream = NULL;
    mm_camera_buf_def_t * m_frame = NULL;
    for (uint32_t i = 0; i < recvd_frame->num_bufs; i++) {
        if (recvd_frame->bufs[i]->stream_type == CAM_STREAM_TYPE_SNAPSHOT) {
            m_frame = recvd_frame->bufs[i];
                      LOGI("soure m_frame->buffer=%p",m_frame->buffer);
            pLLSStream = pChannel->getStreamByHandle(m_frame->stream_id);
            break;
        }
    }

    //获取图像size
    cam_frame_len_offset_t pic_offset;
    memset(&pic_offset, 0, sizeof(cam_frame_len_offset_t));
    cam_dimension_t dim;
    memset(&dim, 0, sizeof(dim));
    //pLLSStream 为 QCameraStream*
    pLLSStream ->getFrameOffset(offset);
    pLLSStream ->getFrameDimension(dim);

        //如果是第一帧,初始化算法
        if(pme->m_iLLSShotNum == 0 ) {
            //初始化参数
            pme->m_parameters.width = pic_offset.mp[0].stride;
            pme->m_parameters.height = pic_offset.mp[0].scanline;
            pme->m_parameters.number_input = 5;
            pme->m_parameters.num_merge = 5;
            pme->m_parameters.y_nr_level = 1.0;
            pme->m_parameters.c_nr_level = 4.0;
            pme->m_parameters.gain = 1.0;
            pme->m_parameters.gamma = 1.0;
            pme->m_parameters.sharpness = 600;
            pme->m_parameters.contrast = 0;
            pme->m_parameters.saturation = 1024;
            pme->m_parameters.sharpness_thres = 0.85;
            pme->m_parameters.obc = 1.0;
            pme->m_parameters.obc_forface = 3.0;
            pme->m_parameters.c_nr_times = 3;
            pme->m_parameters.block_cnr = 0;
            pme->m_parameters.auto_sorting = 1;
//1. 调用 MorphoLowlight_initialize 初始化算法
              LOGI("zcf [KPI Perf]: MorphoLowlight_initialize  begin");
            error = MorphoLowlight_initialize(&pme->m_engine, &pme->m_parameters);
            if(!error)
            LOGI("zcf [KPI Perf]: MorphoLowlight_initialize success! w=%d,h=%d"
            ,pme->m_parameters.width,pme->m_parameters.height);
            //申请输出图像的缓冲区
            pme->m_out_img.width = pic_offset.mp[0].stride;
            pme->m_out_img.height = pic_offset.mp[0].scanline;
            int yuv_size_input = pic_offset.mp[0].stride*pic_offset.mp[0].scanline*3/2;
            pme->m_out_img.dat.semi_planar.y = malloc(yuv_size_input);
            pme->m_out_img.dat.semi_planar.uv = ((unsigned char*)pme->m_out_img.dat.semi_planar.y
                                      + pic_offset.mp[0].stride*pic_offset.mp[0].scanline);
            pme->m_out_img.pitch.p = pic_offset.mp[0].stride;
            pme->m_out_img.pitch.semi_planar.y = pic_offset.mp[0].stride;
            pme->m_out_img.pitch.semi_planar.uv = pic_offset.mp[0].stride;
              LOGH("zcf [KPI Perf]: MorphoLowlight_start begin");

//2.调用 MorphoLowlight_start 设置参数
            error = MorphoLowlight_start(&pme->m_engine, &pme->m_out_img, &pme->m_parameters);

            if(!error)
                LOGI("zcf[KPI Perf]: MorphoLowlight_start success!");
        }

         //set input img params
        morpho_ImageDataEx input_image;
        input_image.width = pic_offset.mp[0].stride;
        input_image.height = pic_offset.mp[0].scanline;
        input_image.dat.semi_planar.y = m_frame->buffer;
        input_image.dat.semi_planar.uv = (unsigned char*)((unsigned char*)m_frame->buffer + 
        pic_offset.mp[0].stride*pic_offset.mp[0].scanline);
        input_image.pitch.p = pic_offset.mp[0].stride;
        input_image.pitch.semi_planar.y = pic_offset.mp[0].stride;
        input_image.pitch.semi_planar.uv = pic_offset.mp[0].stride;

//3.调用MorphoLowlight_addImage 添加照片
        error = MorphoLowlight_addImage(&pme->m_engine,&input_image);

        ++pme->m_iLLSShotNum;//帧数+1
        //如果帧数不等于5帧
        if(pme->m_iLLSShotNum!=5){
          pChannel->bufDone(recvd_frame);//返回这个内存给kernel继续使用
          free(frame);//frame帧数调用malloc申请的,因此直接释放
          frame = NULL;
          return;//不继续往后调用,获取下一帧图像
        }
        //获取了5帧图像了,
        pme->m_iLLSShotNum= 0;//重置照片张数为 0
        int base_index=0;
        //MorphoLowlight_process here
        LOGI("zcf [KPI Perf]: MorphoLowlight_process begin");

//4.调用MorphoLowlight_process进行多帧降噪
        error = MorphoLowlight_process(&pme->m_engine,&base_index);
        if(!error) {//处理成功
        LOGI("zcf [KPI Perf]: MorphoLowlight_process success!base_index=%d",base_index);
        int size = pic_offset.mp[0].stride * pic_offset.mp[0].scanline * 3/2;
        //如果图像有问题,可以dump出来 算法处理前后的图像
        //dumpFile(pme->m_out_img.dat.semi_planar.y,pic_offset.mp[0].stride,
                    //pic_offset.mp[0].scanline,size,base_index);
        //把处理后的数据复制给要返回给上层的buffer
        memcpy(m_frame->buffer,pme->m_out_img.dat.semi_planar.y,size);
        }
//5.调用MorphoLowlight_finalize释放资源
      MorphoLowlight_finalize(&pme->m_engine);

    //add morpho_low_light end
#endif //end of MORPHO_LOW_LIGHT

    // Wait on Postproc initialization if needed
    // then send to postprocessor
    if ((NO_ERROR != pme->waitDeferredWork(pme->mReprocJob)) ||
            (NO_ERROR != pme->m_postprocessor.processData(frame))) {
        LOGE("Failed to trigger process data");
        pChannel->bufDone(recvd_frame);
        free(frame);
        frame = NULL;
        return;
    }    

    LOGH("[KPI Perf]: X");
    ATRACE_INT("[KPI Perf] X zsl_channel_cb: X",1);
}

源码里注释已经写的很清晰了。讲一下有争议的点:
//如果帧数不等于5帧
if(pme->m_iLLSShotNum!=5){
pChannel->bufDone(recvd_frame);//返回这个内存给kernel继续使用
free(frame);//frame帧数调用malloc申请的,因此直接释放
frame = NULL;
return;//不继续往后调用,获取下一帧图像
}
这里是我的做法,我是从recvd_frame里面获取数据的,虽然调用了 pChannel->bufDone(recvd_frame),
告诉kernel可以使用这个recvd_frame内存了,我的理解是,只要不进行下一次拍照,数据就会缓存着,不会被释放!

其他做法
//如果帧数不等于5帧
if(pme->m_iLLSShotNum!=5){
用一个数组来缓存recvd_frame,
最后在统一释放资源
}

三、调试技巧

1.算法的执行时间

LOGI("zcf [KPI Perf]: MorphoLowlight_process begin");
error = MorphoLowlight_process(&pme->m_engine,&base_index);
LOGI("zcf [KPI Perf]: MorphoLowlight_process success!%d");

添加算法执行前后的log,计算出算法执行的时间。
2.dump 算法执行前后的图像

void dumpFile(void* frame,int w,int h,int size,int base_index)
{
    char buf[128];
    int res=0;
    snprintf(buf, sizeof(buf), "%s%d_%dx%d.yuv","/data/misc/camera/",base_index, w, h);
    int file_fd = open(buf,O_RDWR | O_CREAT,0777);
    if(file_fd == -1){ 
        LOGI("zcf open file failed file_fd=%d",file_fd);
    }    
    res = write(file_fd,frame,size);
    if(res !=size)
        LOGI("zcf dumpFile failed!res=%d",res);
    close(file_fd);
}

如果加入算法时,图像有异常,可以dump相关图片看看。
路径:/data/misc/camera/

四、结果

【Camera专题】HAL1- 多帧降噪算法的集成(实战1)_第3张图片

继续当一名咸鱼(* ̄︶ ̄)!

Stay Hungry,Stay Foolish!

你可能感兴趣的:(【Camera专题】HAL1- 多帧降噪算法的集成(实战1))