正好在android下使用v4l2打开usb相机,然后发现使用v4l2设置自动曝光没有反应,之前明明有用的,不知道改动了什么,想了一下,就自己写了一个简单的自动曝光,这样就不用看v4l2的脸色行事了。
理论基础
我这里主要就是计算图像的中间灰度值,当然不是全图,我这里区中间一块矩形,然后计算中间灰度值,然后同测试,看图像和中间灰度值,通过调整曝光时间,得出一个最佳值,然后达到最佳值附近即可。
然后我们运行的时候就可以通过调节曝光值来时图像中间灰度值接近最佳值即可。
中间灰度值
首先我们要写一个中间灰度值计算的函数,我这里的码流是yuyv的,如果你是其他码流格式,就需要相应更改代码了
/**
* 计算图像指定部分区域的直方图,并得到这部分图像的中间灰度值
* @param psImage 图像
* @param w 宽
* @param h 高
* @param iX 矩形左上角x坐标
* @param iY 矩形左上角Y坐标
* @param iW 矩形宽
* @param iH 矩形高
* @return 中间灰度值
*/
int GetMidVFromImHist_yuyv(unsigned char *psImage, int w, int h, int iX, int iY, int iW, int iH)
{
if (iX < 0 || iY < 0 || iX + iW > w || iY + iH > h)
return -1;
int iMidValue = 0;
int piHistgram[256];
memset(piHistgram, 0, 256 * sizeof(int));
//计算指定部分区域的灰度直方图
//为了加快速度,隔一行和一列计算
unsigned char *pTTT;
for (int i = iY; i < iY + iH; i += 2)
{
pTTT = psImage + 2 * i * w + 2 * iX;
for (int j = 0; j < iW; j += 2, pTTT += 2 * 2)
piHistgram[(*pTTT)]++;
}
//得到中间灰度值
int iPixSum = 0;
int iSum_1 = ((((iW + 1) >> 1) * ((iH + 1) >> 1)) >> 1);
for (int i = 0; i < 256; i++)
{
iPixSum += piHistgram[i];
if (iPixSum > iSum_1)
{
iMidValue = i;
break;
}
}
return iMidValue;
}
自动曝光
然后我们需要实现一个通过图片变更自动曝光值得函数,下面贴代码,有几个全局变量吧,实际使用,最好要放到类里面,函数也放到类里面。
#define AUTOEXPO_IMAGE_NUM 1 //几帧图像,调整一次曝光时间.
#define BSET_MIDVALUE 175 //图像最佳的中间灰度值
#define MAX_MIDVALUE_GAP 5 //不需要调整曝光时间的中间灰度值上下限
#define MAX_EXPO_ADJUST_STEP 0.3 //调节曝光时间时,最大的步长比例
int m_iCurExpoTime; //当前的曝光时间值
int m_iImageNum1; //当灰度值调节到合适的位置,通过设置这个数来减少计算量
/**
* 初始化
* @param iExpoTime 最初的曝光时间值
*/
void initAutoExposure(int iExpoTime)
{
m_iCurExpoTime = iExpoTime; //当前的曝光时间值
m_iImageNum1 = 0;
}
/**
* 设置曝光值
* @param curExpoTime 曝光值
*/
void setExposureTime(int curExpoTime)
{
struct v4l2_control ctrl;
ctrl.id = V4L2_CID_BRIGHTNESS;
int bright = ioctl(fd, VIDIOC_G_CTRL, &ctrl);
ctrl.value = curExpoTime;
int ret = ioctl(fd, VIDIOC_S_CTRL, &ctrl);
if(ret!=0){
//设置失败
}
}
/**
* 进行一次自动曝光操作(需要在虹膜检测函数GetTwoIrisFromBigImage_New_Mlt之后执行)
* @param psBigImage 原始大图
* @param w 宽
* @param h 高
*/
void AutoExposure(unsigned char *psBigImage, int w, int h)
{
if(!psBigImage)return;
//每 AUTOEXPO_IMAGE_NUM 帧图像,调整一次曝光时间
m_iImageNum1++;
if (m_iImageNum1 >= AUTOEXPO_IMAGE_NUM)
{
//下一张图片继续计算
m_iImageNum1 = 0;
//小于和大于正好一半,对应的灰度值
int iMidValue = -1;
//先确定左边的固定区域
int iW = w >> 1;
int iH = h >> 1;
int iX = w >> 2;
int iY = h >> 2;
//计算对应区域的直方图,得到中间灰度值
iMidValue = GetMidVFromImHist_1(psBigImage, w, h, iX, iY, iW, iH);
int iMax_M, iMin_M, iBest_M;
iBest_M = BSET_MIDVALUE;
iMax_M = iBest_M + MAX_MIDVALUE_GAP;
iMin_M = iBest_M - MAX_MIDVALUE_GAP;
//图像太亮,减少曝光时间
if (iMidValue > iMax_M)
{
//接近最佳值时,调整幅度较小
double dDiff = ((double)(iMidValue - iBest_M)) / ((double)(iBest_M * 3));
//调整步长不能太大,防止过冲
if (dDiff > MAX_EXPO_ADJUST_STEP)
dDiff = MAX_EXPO_ADJUST_STEP;
//曝光时间的改变值
int iExpo_Diff = (int)(dDiff * ((double)m_iCurExpoTime) + 0.5);
if (iExpo_Diff < 2)
iExpo_Diff = 2;
int iNewExpoTime = m_iCurExpoTime - iExpo_Diff;
//小于50图像太黑了
if (iNewExpoTime < 50)
iNewExpoTime = 50;
if (iNewExpoTime != m_iCurExpoTime)
{
m_iCurExpoTime = iNewExpoTime;
//设置曝光时间
setExposureTime(m_iCurExpoTime);
}
}
else if (iMidValue < iMin_M)//图像太暗,增加曝光时间
{
double dDiff = ((double)(iBest_M - iMidValue)) / ((double)(iBest_M * 3));
//调整步长不能太大,防止过冲
if (dDiff > MAX_EXPO_ADJUST_STEP)
dDiff = MAX_EXPO_ADJUST_STEP;
int iExpo_Diff = (int)(dDiff * ((double)m_iCurExpoTime) + 0.5);
if (iExpo_Diff < 2)
iExpo_Diff = 2;
int iNewExpoTime = m_iCurExpoTime + iExpo_Diff;
//大于1000图像太亮了
if (iNewExpoTime > 1000)
iNewExpoTime = 1000;
if (iNewExpoTime != m_iCurExpoTime)
{
m_iCurExpoTime = iNewExpoTime;
setExposureTime(m_iCurExpoTime);
}
}
else{
//十张图片之后再重新计算
m_iImageNum1 = -10;
}
}
}
加上上面的计算中间灰度值函数,完整,然后在打开相机是调用初始化,然后在码流获取后,调用自动曝光函数即可实现自动曝光。
注意BSET_MIDVALUE这个数对应的相机和环境有差异,需要自行测试设置。
这个不是完整的工程,就没有上传github了。