系列文章
动手入门第三方算法集成系列:
【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.算法商提供的文件
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
- 编写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/
四、结果
继续当一名咸鱼(* ̄︶ ̄)!