mainActivity如下:
package c.c; import java.io.IOException; import java.util.Iterator; import java.util.List; import android.app.Activity; import android.content.ContentResolver; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.hardware.Camera; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Bundle; import android.provider.MediaStore; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.View.OnClickListener; import android.widget.RelativeLayout; /** *需求描述: *利用SurfaceView预览,且拍照. *将拍摄后的图片保存至相册 * *注意 *1 权限 * <uses-permission android:name="android.permission.CAMERA"/> *2 屏幕旋转后SurfaceView中预览的图片有90度的旋转 * 解决方式: * 2.1将屏幕设置为竖屏 * setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); * 2.2设置相机的Orientation * mCamera.setDisplayOrientation(90); * 注意: * 该问题虽然解决了,但是会带来以下的一个问题. *3 拍照保存的图片可能被旋转90度. * 解决方式: * http://blog.csdn.net/walker02/article/details/8211628 * http://www.cnblogs.com/andgoo/archive/2012/08/29/2661896.html * * 此Demo中如下解决该问题 * * 关于以上问题解决的说明: * 1 相机拍照默认的是横屏的 * 以ME525+为例,默认的是LOGO在左边 * 平常的竖屏LOGO在上边.所以,需要将相机旋转90度. * 即mCamera.setDisplayOrientation(90); * 这样才能在横竖屏切换后预览画面正常显示. * 2 最好不要在代码中设置Activity以什么方式(横/竖屏)显示,比如: * setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); * 应该写在配置文件中,这样效率更高. * 3 现在已经把Activity设置为竖屏了.所以onConfigurationChanged() * 方法已经不能起到期望的作用. * 但是还需要监听现在屏幕是横屏还是竖屏.所以在此采用了重力感应的 * 方式来实现.应该还有其他更好的办法,待调查. * 4 小bug:LOGO朝右边的时候,拍出的照片仍然被旋转了 */ public class MainActivity extends Activity implements SurfaceHolder.Callback,OnClickListener,Camera.PictureCallback{ private SensorManager mSensorManager; private AccSensorListener mAccSensorListener=null; private Sensor mSensor; private SurfaceView mSurfaceView; private SurfaceHolder mSurfaceHolder; private Camera mCamera; private float y=0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //设置Activity竖屏显示 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); setContentView(R.layout.main); init(); } private void init(){ mSensorManager=(SensorManager) getSystemService(SENSOR_SERVICE); mSensor=mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mSensorManager.registerListener (new AccSensorListener(), mSensor, SensorManager.SENSOR_DELAY_NORMAL); mAccSensorListener=new AccSensorListener(); mSurfaceView=(SurfaceView) findViewById(R.id.surfaceView); mSurfaceView.setFocusable(true); mSurfaceView.setFocusableInTouchMode(true); mSurfaceView.setClickable(true); mSurfaceView.setOnClickListener(this); mSurfaceHolder=mSurfaceView.getHolder(); //设置该SurfaceView是一个"推送"类型的SurfaceView mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); mSurfaceHolder.addCallback(this); } @Override protected void onDestroy() { super.onDestroy(); //取消重力感应 mSensorManager.unregisterListener(mAccSensorListener); } private class AccSensorListener implements SensorEventListener{ public void onSensorChanged(SensorEvent event) { //System.out.println("xxxxxx event.values[1]="+event.values[1]); y=event.values[1]; } public void onAccuracyChanged(Sensor sensor, int accuracy) { } } //2 parameters.getSupportedXXX的到相机支持的一些效果或者模式,比如: //parameters.getSupportedPictureFormats(); //parameters.getSupportedColorEffects(); //parameters.getSupportedFocusModes(); //parameters.getSupportedWhiteBalance(); public void surfaceCreated(SurfaceHolder holder) { final int LARGEST_WIDTH=200; final int LARGEST_HEIGHT=200; int bestWidth=0; int bestHeight=0; mCamera=Camera.open(); Camera.Parameters parameters=mCamera.getParameters(); //操作1 为相机设置某种特效 List<String> colorEffects=parameters.getSupportedColorEffects(); Iterator<String> iterator1=colorEffects.iterator(); while (iterator1.hasNext()) { String effect = (String) iterator1.next(); if (effect.equals(Camera.Parameters.EFFECT_SOLARIZE)) { //若支持过度曝光效果,则设置该效果 //parameters.setColorEffect(Camera.Parameters.EFFECT_SOLARIZE); break; } } //操作2 改变SurfaceView的大小 List<Camera.Size> previewSizes=parameters.getSupportedPreviewSizes(); if (previewSizes.size()>1) { Iterator<Camera.Size> iterator2=previewSizes.iterator(); while (iterator2.hasNext()) { Camera.Size size = (Camera.Size) iterator2.next(); if (size.width>bestWidth&&size.width<=LARGEST_WIDTH&& size.height>bestHeight&&size.height<=LARGEST_HEIGHT) { bestWidth=size.width; bestHeight=size.height; } } if (bestWidth!=0&&bestHeight!=0) { parameters.setPreviewSize(bestWidth, bestHeight); mSurfaceView.setLayoutParams (new RelativeLayout.LayoutParams(bestWidth, bestHeight)); } } //操作3 当屏幕变化时,旋转角度.否则不对 //因为刚开始时竖屏的 mCamera.setDisplayOrientation(90); //操作结束 try { //将摄像头的预览显示设置为mSurfaceHolder mCamera.setPreviewDisplay(mSurfaceHolder); } catch (IOException e) { mCamera.release(); } //设置输出格式 parameters.setPictureFormat(PixelFormat.JPEG); //设置摄像头的参数.否则前面的设置无效 mCamera.setParameters(parameters); //摄像头开始预览 mCamera.startPreview(); } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { mCamera.startPreview(); } public void surfaceDestroyed(SurfaceHolder holder) { mCamera.stopPreview(); mCamera.release(); } //处理SurfaceView的点击事件 public void onClick(View v) { mCamera.takePicture(null, null, this); } public void onPictureTaken(byte[] data, Camera camera) { //将图片保存至相册 if (y>0) { System.out.println("竖屏 竖屏 竖屏 竖屏 竖屏 竖屏 "); ContentResolver resolver = getContentResolver(); Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); Matrix matrix=new Matrix(); matrix.setRotate(90); Bitmap bitmap2=Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); MediaStore.Images.Media.insertImage(resolver, bitmap2, "xxoo", "des"); }else { System.out.println("横屏 横屏 横屏 横屏 横屏 横屏 "); ContentResolver resolver = getContentResolver(); Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); MediaStore.Images.Media.insertImage(resolver, bitmap, "xxoo", "des"); } //mSensorManager.unregisterListener(mAccSensorListener); //拍照后重新开始预览 camera.startPreview(); } }
main.xml如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <SurfaceView android:id="@+id/surfaceView" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </RelativeLayout>
目录:
前言
驱动篇:
1、 Micron sensor ISP的原理图
2、 sensor 的原理框架
3、Sensor 的初始化步骤
4、Preview时候的sensor设置
5、Capture时候的sensor设置
6、工频干扰的调试
7、亮度以及夜景模式
调试篇:
1、 清晰度的测试
2、 灰阶重现
3、 画面的均匀性以及暗脚补偿
4、 畸变
5、 白平衡的调试
前言
Micron sensor 是我们公司所用最多的图像传感器,也是目前市场上评价很高的主流sensor产品。写这篇文章的目的在于让后继调试sensor者对sensor的调试有初步的思路和对micron sensor的一些特性有一定的了解,希望以后的调试工作能够少走一些弯路。具体的sensor的工作原理和更深入的图像工程方面的知识,可以参看各个sensor的datasheet和上网查找一些关于camera的测试资料。
驱动篇:
Micron sensor ISP的原理图:
下图是sensor的功能框架构图:
Sensor Core register是实际上控制sensor的register.是直接控制sensor的寄存器(对应的是sensor 寄存器的page 0)。
Image Flow Processor 里的register主要是一些控制sensor的算法的寄存器。其中color Pipeline主要是对输出数据和信号的一些控制。比如 Base configuration, lens shading, resize, output format(page 1)
Camera control集中了对sensor core的控制算法,控制sensor core的工作都是在这个寄存器组中完成。(page 2)比如AE, AWB, Flicker, Camera control sequencer。
Sensor 的初始化步骤:
一般sensor的初始化通常包含以下几个步骤:
1、 sensor的上电。Micron sensor的电源分为数字电源,模拟电源和IO电压。这三个电源并没有严格的先后上电顺序,可以在代码中同时打开。
2、 对sensor输出MCLK,配置对PCLK采样输出频率,这是能否正常接收sensor数据的关键。
3、 配置V,H同步信号的输出极性,如果极性配置不对,将造成图象不能正常采集,自然显示混乱。
4、 硬件的reset。 Micron sensor的reset为低reset,并且至少持续1US,
5、 软件的reset。既然为软件的reset,那就必须要求BB或者多媒体MAP能够对sensor进行写寄存器。也就是要保证IIC能够正常地写数据进入sensor ISP,这点是保证软件能够进行调试的基础。软件reset通常根据sensor的不同会有所变化,如mt9d111内带一个MCU,所以在reset的时候要对MCU同时进行reset。而mt9m11就没有带MCU。注意硬件reset后要保留一些时间才能使用IIC总线。通常在10个US以上。
6、 Micron sensor mtd9111 系列的带ISP的2M sensor在ISP中默认了一组寄存器,能够在reset后不用IIC写任何寄存器就能输出图象,这个时候sensor的input clock是output clock的两倍,前期可以用这个方法来验证硬件和软件供电,复位等是否正确,当后端接受的图象engine只能用mclk来同步工作时候,必须要正确配置接收的采样频率,否
则不能得出正确的图象。
上图就是采样频率不匹配的现象。请注意与YUV,RGB顺序配置错误的现象有什么区别。下图是顺序倒置:
7、 写入micron 工程师给的初始化寄存器,并配置输出频率和输出图象的分辨率。
8、 读取sensor的版本号,如果与我们所用产品的version一致,就代表初始化工作正确完成。
初始化sensor的道理很简单,而且如果平台比较成熟,有可能一次性就能正确的初始化,也有可能会花很多时间去查问题,特别是如果你遇到了iic写给sensor的时候出现不稳定的现象或者sensor接收到iic命令和数据,却不按正常地输出,那就比较麻烦了,不过2m sensor的ISP既然已经带了MCU,不妨把它当成一个应用处理器去维护,在写某些改变sensor内部工作状态的寄存器时要注意延时。有些时候对一个寄存器可以多次写入保证其能正常地工作,这是一个还没有想出原因的经验。按照以上的流程检查应该能够准确定位80%以上的问题。
IIC 总线协议
I2C串行总线有两根信号线:一根双向的数据线SDA;另一根是时钟线SCL。所有接到I2C总线上的设备的串行数据都接到总线的SDA线,各设备的时钟线SCL接到总线的SCL。在Vienna平台上, I2C连了两个器件,分别是sensor和audio codec,,主控设备(MAP)发送不同的器件ID与两个设备进行数据传输。
在I2C总线传输过程中,将两种特定的情况定义为开始和停止条件,当SCL保持“高”,SDA由“高”变为“低”时为开始条件;SCL保持“高”,SDA由“低”变为“高”是为停止条件。开始和停止条件由主控器产生。使用硬件接口可以很容易地检测开始和停止条件,没有这种接口的微机必须以每时钟周期至少两次对SDA取样以使检测这种变化。
下图对就是IIC的一次写操作,具体的IIC协议很容易在网站上可以找到,micron 的datasheet也能找到一些介绍。
Preview时候的sensor设置:
在preview的时候为了得到更高的帧率,通常采用低分辨率的输出,也就是长宽都只有最高分辨率的一半,本来能够设置更小的分辨率输出,这个时候sensor的输出是间隔输出,并不是对象素采样后的均匀输出。但是由于多媒体芯片corelogic的无法接收不规则的pclk的原因,只有作罢。而有些平台的camera interface就不存在这个问题。从而能直接输出屏幕大小的图象,减少后端处理的繁琐和节约为preview所开的buffer大小。
Capture时候的sensor设置:
Capture为了获得更大的分辨率和更好的图象质量,所以必须采用高分辨率的输出,那么在切换到capture的时候就需要对sensor进行一组寄存器设置,micron sensor 为用户提供了两个相对独立的context,能够存两个寄存器组,默认的设置是将preview用context A, capture 用context B, context A通常是low power mode,而context B用的是full power mode ,由于corelogic只能用mclk去同步采样,这样它便只能接受规则的pclk,不然采样就会有问题,那么这样就要求context
A与context B的都为full power mode,这样就能保证无论preview还是capture,sensor都能以已固定的频率的输出pclk,后端MAP就能正常地接收图象信息,不至于出现花屏和颜色不对。由于这样的解决办法并不是micron 推荐的方案,而是自己想出的弥补方法。所以Vienna平台上至少一半以上的问题都是由corelogic的这个缺陷,以及为了弥补这个缺陷所修改的sensor输出引起的,以后如果要换多媒体应用处理器,请考虑到MAP是否支持接受不规则的pclk。目前Vienna 和高通平台都不支持变化的PCLK,而Vision平台是支持的。
lower power 模式下的输出
full power 模式下的输出
证明corelogic不能接受lower power mode的sensor输出。
由于输出的时钟频率preview与capture固定,CMOS sensor暴光原理是行暴光,暴光时间等于行暴光时间,line time=hsync time +hblank time。130万象素的的sensor为例,preview的时候输出VGA,hsync time=640*k, K为shutter width(快门时间)。而capture的时候hsync time=1280*k, 在k不变的情况下,hsync time发生了巨大的变化,这样暴光时间也发生了巨大的变化,现象是拍照的时候的图片明显过曝。不过micron
给出了一个可以改变shutter width的寄存器,通过改变这个寄存器能够调整综合暴光时间,这样就能解决这个问题,在老化测试的时候经常出现暴光不对,就是因为这个寄存器没写入或写入后sensor没有反映造成的,由于preview->capture->preview中间的转化值都是用软件来实时计算出来的,所以无论是iic读或者写,还是环境亮度引起sensor亮度计算错误,都会影响到暴光,维护这段代码的时候要特别小心。如果遇到拍照的时候与preview的图像质量差别很大,请从这段代码开始查。
// sensor在进行模式切换
IIC_Write16bit(0xf0, 0x0001);
IIC_Write16bit(0xC6, 0xA104);
//判断模式切换是否成功
dataTemp=IIC_Read16bit(0xc8);
if(g_nightmode)
{
waittime=1000;
}
else
{
waittime=50;
}
//判断模式切换是否成功,请注意这个不一定会成功。
while (dataTemp != 7)
{
dataTemp = IIC_Read16bit(0xC8);
WaitTime_ms(1);
i++;
if(i>waittime)
{
AMOIT(" the sensor change mega mode fail!-----/n");
break;
}
}
AMOIT1("------i=%d-----/n",i);
//以下这段就是在进行暴光控制
IIC_Write16bit(0xf0, 0x0000);
dataTemp = IIC_Read16bit(0x09);
gCurbrightness1 = dataTemp;
IIC_Read16bit(0x09);
WaitTime_ms(10);
IIC_Write16bit(0x09, dataTemp*2/7);
IIC_Write16bit(0xf0, 0x0001);
IIC_Write16bit(0xc6, 0x2225);
dataTemp = IIC_Read16bit (0xc8);
gCurbrightness2 = dataTemp;
WaitTime_ms(10);
IIC_Write16bit(0xf0, 0x0000);
IIC_Write16bit(0x65, 0xB000); // CLOCK_ENABLING
IIC_Write16bit(0x65, 0xE000); // CLOCK_ENABLING
WaitTime_ms(600); // Wait 1 frame time
preview的时候输出的图象。
capture 下来的图象,可以看见明显的过暴现象。
根据经验并非模式切换失败就一定不能正常输出百万象素,而不能正常输出多半就是模式切换失败。模式切换的时间与成功率与帧率有一定的关系,一般说来,帧率越快,时间越短,成功率越高。再从capture->preview的时候也必须设置暴光值,以保证图像不会突然变暗,如果发现拍照越来越暗,多半就是返回preview的时候设置失败。
注意:模式切换的时候,用示波器可以看到sensor在做切换的时候会出现突然拉低VSNYC信号,形成一个较长的消隐(blank)信号,然后输出另外一个MODE的下的信号,有时候不稳定的现象就是这个blank信号过长,特别是发生在低帧率的情况下,会使后端ISP或者DSP无法采集到数据,没有办法产生拍照的中断,造成task被挂起(进入 idle task)或者死机重启(被狗咬)的现象,具体原因和对策可以根据不同平台的实现方法去分析解决。
到了这里sensor的两个基本状态的调试就算基本完成了,后期就是对这段代码的维护工作。
工频干扰:
如果手机出现以如下图的这种水波纹就是工频干扰。工频干扰是由于室内日光灯闪烁造成的。CMOS与CCD 两种不同的工艺制造出来的sensor工频干扰现象是不一样的,这是由暴光的方式不同造成的。
CMOS是行暴光,也就是在每行暴光时间决定了画面的亮度,举例:一个50HZ的光源,电压曲线为正弦曲线,那能量曲线定性分析可以认为是取了绝对值的电压曲线。那就是能量做1/100秒的周期变化。那就要求暴光的时间必须是1/100秒的整数倍。如果没有把暴光时间调整到1/100秒的整数倍,就有可能会有每行的暴光值不一样,造成同一个image上有水波纹现象。CCD是整帧同时暴光,所以,工频干扰表现的就是图像有轻微的闪烁。产生的原理与CMOS sensor的原理相似。
如果有发现这样的问题,可先计算出暴光时间,再在这个基础上进行微调。相信很快就能调到没有工频干扰。
Micron 有个寄存器能够调整暴光,以达到消除工频干扰的目的。
算法:line time *0x58(page 2)/PCLK=N/100(用这个公式算出来的值还要进行微调试,reg 0x58是mt9m111的寄存器,不同的sensor是不一样的,但一定能找到一个类似的寄存器,N是自然数,datasheet里面没有介绍,呵呵,不过如果你了解cmos的暴光原理,相信很容易明白的)。
亮度以及夜景模式:
相信现在大家都知道图象的亮度与暴光时间相关,所以为了让暗处的图片能够清晰地显示必须增加sensor对暗处图象的暴光时间,也就是line time会设置得比普通模式的时候要大许多,这样能使CMOS sensor拥有更多的暴光时间,从而提高亮度。
上图为没有采用夜景模式的照片,下图为使用夜景模式的照片
我们有两种方法来控制图像亮度,一种是使用AE target,一种是加大灰度增益。我们使用的是AE target方式,这样的图片色彩更逼真。用这种办法会影响到frame rate,当帧率达到我们限制的极限的时候,就要用增加模拟增益来做了,这样会同时放大图像噪声。一般不用增加数字放大增益来调整图像亮度。
调试篇
与一般IC的驱动不同,sensor除了要工作起来和稳定性之外,还需要调试图象质量。在这点上,所有的sensor都要经过相同的评估测试,调试。通常camera的调试会有下面主要几个方面:
清晰度的测试
使用ISO12233标板测试
中心垂直分辨率
中心水平分辨率
主要看肉眼刚好能够分清线条时候的刻度值。
清晰度主要由sensor制作工艺水平和镜头参数决定,但可以通过调试锐度(sharp)来增强清晰度。
副作用是较高的锐度对图像的平滑性有影响,会使物体边缘特别明显,甚至出现锯齿现象。
灰阶重现测试
一般sensor都会有一组寄存器用来调整gamma曲线,也就是我们所说的gamma table,由于本人水平有限,这个调试一般由micron 的工程师完成。
gamma 曲线图
画面的均匀性以及暗脚补偿:
通过上图我们可以发现画面不是很均匀,中心和边缘的亮度有明显的差别,由于镜头的原因,sensor总是中间的象素暴光比较充分。Micron 能够调试lens shading来解决这个问题,能将画面调试得更加均匀。
调试过后的图片
这个调试过程中要防止出现光圈现象。
畸变:
畸变是由镜头形成的,由camera的制造工艺决定,所以无法通过改变ISP的设置改善。
下图为测试畸变的样板。
白平衡:
要说到白平衡就要先建立色温的概念。
其实在摄影领域,光源大多是根据它们的色温来定义。色温的单位是开尔文,在不同温度下呈现出的色彩就是色温。当一个黑色物体受热后便开始发光,它会先变成暗红色,随着温度的继续升高会变成黄色,然后变成白色,最后就会变成蓝色(大家可以观察一下灯泡中的灯丝,不过由于受到温度的限制,大家一般不会看到它变成蓝色)。总之,这种现象在日常生活中是非常普遍的。
上图就是不同的色温的光源照射下的同一组物体的图片。
人的大脑能仔细分析出从眼睛接受的信号,因而能感知不同的色温(color temperature)来显示相同的白色。但camera 却不能,在早晨时分的相片偏红,而黄昏时候的却偏黄,就算同一张白纸在不同的环境下被拍摄,如不同的时间,不同的光源,都会出现不同程度的偏差。
调整白平衡,就是要给白色一个定义,能正确记录我们眼睛所看到的颜色。 Micron sensor 给出了两种白平衡的控制方法,一种是自动白平衡(AWB),一种是手动白平衡(MWB)。
自动白平衡为sensor内部ISP的默认设置,ISP中有一结构复杂的矩形图,它可决定画面中的白平衡基准点,以此来达到白平衡调校。由于手机camera不属于色彩要求很高的照相机范畴,所以我们一般使用自动白平衡,自动白平衡在光源不是特别复杂的时候有较好的效果。
手动白平衡需要自己设置R,G,B的gain值,micron给我们的寄存器中有专门设置这三个值的寄存器。
作者水平有限,或许会有些不准确之处,欢迎指正批评。