Android开发:setContentView切换界面,自定义带CheckBox的ListView显示SQlite条目—–实现
最后更新于:2022-04-01 07:24:21
问题背景:
我在其他Activity里有一个数据库,里面有若干条目,数据库里存的是最简单的“名字”string类型的信息。我在另外一个Activity里,通过按键Button,显示出一个带checkbox的列表,显示出数据库里的姓名,然后可以选中多个。类似于文件夹删除的功能。
下面是实现:
**第一部分,在布局文件夹下新建一个my_checkbox.xml.**
这个布局是用来控制将来listview里,每一行怎么显示。在这里我是左边显示名字,右边显示复选框CheckBox。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:id="@+id/item_text"
android:textSize="25dip"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<CheckBox
android:id="@+id/item_check"
android:textSize="25dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:focusable="false"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"/>
</LinearLayout>
**注意:**
1,上面的TextView里的layout_weight=1用来实现最左边显示名字,最右边显示CheckBox。
2,由于CheckBox的响应优先级高于ListView,这里需要把CheckBox的clickable和focuseable属性都关闭。将来只通过listview的item是否点击来判断。
3,CheckBox的checkMark用来设置当选中之后,是个什么效果。
**第二部分:新建一个布局list_check.xml,用来显示数据库的条目。当在主Activity里点击后就会切换到这个界面。**
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<Button
android:id="@+id/confirmBtn"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="确定" />
<ListView
android:id="@+id/checkList"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
</ListView>
</LinearLayout>
**注:**上面的button是选中若干条目后的确定按键,同时也是返回按键,返回到主界面。
**第三部分**:在主Activity里设置利用setContentView的方法切换页面。
这里主Activity的布局文件就不提供了。
下面是主Activity的代码:
~~~
//为了实现新的布局
Button mChoseBtn = null;
Button mConfirmBtn = null;
boolean firstFlag = true;
ListView list2 = null;
View checkListView = null;
View mainView = null;
/**Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LayoutInflater inflater = this.getLayoutInflater();
checkListView = inflater.inflate(R.layout.list_check, null);
mainView = inflater.inflate(R.layout.main, null);
setContentView(mainView);
//切换布局监听
mChoseBtn = (Button)mainView.findViewById(R.id.choseBtn);
mChoseBtn.setOnClickListener(new ButtonListener());
setUpViews();
}
class ButtonListener implements OnClickListener{
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()){
case R.id.choseBtn:
Jump2CheckList();
break;
case R.id.confirmBtn:
String s = getCheckInfo();
showToast("您选中的姓名有:"+ s);
Jump2Main();
break;
default:
break;
}
}
}
~~~
~~~
/*切换到主布局*/
public void Jump2Main(){
setContentView(mainView);
setUpViews();
}
~~~
~~~
/*切换到选中布局*/
public void Jump2CheckList(){
setContentView(checkListView);
if(firstFlag){
mConfirmBtn = (Button)checkListView.findViewById(R.id.confirmBtn);
mConfirmBtn.setOnClickListener(new ButtonListener());
firstFlag = false;
}
initCheckList();
}
~~~
**第四部分:给ListView写适配器**,其实很简单 也就是上面的initCheckList函数,包含初始化ListView和适配器两个部分。先看源码:
~~~
public void initCheckList(){
list2 = (ListView)(checkListView).findViewById(R.id.checkList);
list2.setItemsCanFocus(false);
list2.setAdapter(new CheckListAdapter(this, cursor));
list2.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
list2.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> arg0, View view, int positon,
long id) {
// TODO Auto-generated method stub
ViewHolder vHolder = (ViewHolder) view.getTag();
vHolder.check.toggle();
isSelected.put(positon, vHolder.check.isChecked());
}
});
}
~~~
下面是适配器:
~~~
/*给CheckList设置适配器*/
public static Map<Integer, Boolean> isSelected;
public class CheckListAdapter extends BaseAdapter{
private Context mContext;
private Cursor mCursor;
//构造函数
public CheckListAdapter(Context context, Cursor cursor){
mContext = context;
mCursor = cursor;
isSelected = new HashMap<Integer, Boolean>();
for(int i=0; i<mCursor.getCount(); i++){
isSelected.put(i, false);
}
}
public int getCount() {
// TODO Auto-generated method stub
return cursor.getCount();
}
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return null;
}
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return 0;
}
public View getView(int position, View convertView, ViewGroup arg2) {
// TODO Auto-generated method stub
ViewHolder holder = null;
if(convertView == null){
holder = new ViewHolder();
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.my_checkbox, null);
holder.text = (TextView) convertView.findViewById(R.id.item_text);
holder.check = (CheckBox)convertView.findViewById(R.id.item_check);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder)convertView.getTag();
}
mCursor.moveToPosition(position);
holder.text.setText(Integer.toString(mCursor.getInt(0)));
holder.text.append(mCursor.getString(1));
holder.check.setChecked(isSelected.get(position));
return convertView;
}
public final class ViewHolder{
public TextView text;
public CheckBox check;
}
}
~~~
注:
1,initCheckList里要设置相应的参数,如多选等。
2,public static Map<Integer, Boolean> isSelected; 这是一个**全局变量**,且是静态的,用来存储checkbox的选中状态。[http://mobile.51cto.com/android-254823.htm](http://mobile.51cto.com/android-254823.htm) 这里将其设成适配器里的一个静态变量,但奇怪的是到我这就不中了,暂且弄成全局的吧。
3,在适配器的构造函数里初始化上面这个变量,并且所有都设成未选中。
isSelected = new HashMap<Integer, Boolean>();
for(int i=0; i<mCursor.getCount(); i++){
isSelected.put(i, false);
4,由于我跟数据库做了关联,所以在适配器的构造函数里传进去一个cursor,关于cursor的理解可以参考[http://www.cnblogs.com/TerryBlog/archive/2010/07/05/1771459.html](http://www.cnblogs.com/TerryBlog/archive/2010/07/05/1771459.html) 说白了他就是一组信息,是个集合。通过cursor.getCount获得他有多少行的信息。cursor.MoveToposition(i)定位到第i行。获得数据的方法跟数据时建的表的结构有关。
5,
public final class ViewHolder{
public TextView text;
public CheckBox check;
}
至于这个viewholder,实际上不用也可以。他就是把每一行的元素集成了一下,跟布局相对应。但到后来莫名其妙的要用到View.setTag()和View.getTag()来传递数据,所以我又把他加上了。
6,适配器的两大关键。
**第一个是**:
public int getCount() {
// TODO Auto-generated method stub
return cursor.getCount();
}
这里是返回list有多少行。调用**ListView.getCount()**实际上就是在调用这个函数。
**第二个就是**:public View getView(int position, View convertView, ViewGroup arg2)这个函数。注意第二个参数convertView就是给每一行设置的布局。通过
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.my_checkbox, null);
将第一个布局文件,和view关联起来。至于convertView.setTag(holder);其实是给view贴标签,也就是传数据的一种方式。当这个布局不为空时通过holder = (ViewHolder)convertView.getTag();直接获得。mCursor包含所有行的信息,
mCursor.moveToPosition(position);这句话将其定位到position的位置,
holder.text.setText(Integer.toString(mCursor.getInt(0)));
holder.text.append(mCursor.getString(1));
这两句话是获得每行的信息。我这个表的结构是第一列(对应索引为0)是一个int,第二列(索引为1)是一个string。
holder.check.setChecked(isSelected.get(position));这句话是设置checkbox的状态。 也就是说通过isSelected来设定,他的初始态是全不选。所以每次跳转时,默认的是都不选。
最后程序return convertView;返回这个view,然后当下次调用时,他又当做参数传进来。
7,解释下listview设置监听:
list2.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> arg0, View view, int positon,
long id) {
// TODO Auto-generated method stub
ViewHolder vHolder = (ViewHolder) view.getTag();
vHolder.check.toggle();
isSelected.put(positon, vHolder.check.isChecked());
}
});
当每个item被点击时,通过被点击的view.getTag获得具体的控件view, CheckBox的状态反转。
isSelected.put(positon, vHolder.check.isChecked());这句话是通过访问position位置的CheckBox的状态来更新保存信息的isSelected。
8,**最后就是我怎么获得选中的信息?**
可以这么写:
~~~
OnClickListener bPop = new OnClickListener() {
@Override
public void onClick(View v) {
for(int i=0;i<list.getCount();i++){
if(MyAdapter.isSelected.get(i)){
ViewHolder vHollder = (ViewHolder) list.getChildAt(i).getTag();
Log.i(TAG, "--onClick --"+vHollder.title.getText());
}
}
}
};
~~~
通过list.getCount进行遍历,通过list.getChildAt(i).getTag得到被选中的ViewHolder,然后得到信息。 因为这里我跟数据库挂了钩,所以我直接读Cursor里面的信息就可以了。
我的写法是:
~~~
public String getCheckInfo()
{
String info = "";
for(int i=0; i<list2.getCount(); i++){
if(isSelected.get(i)){
//ViewHolder holder = (ViewHolder)list2.getChildAt(i).getTag();
cursor.moveToPosition(i);
info+=cursor.getInt(0)+".";
}
}
return info;
}
~~~
上面的list2.getCount和cursor.getCount是一样的效果。
最后说下这个Cursor,因为他包含了数据库里所有行的信息,所以我直接用他来填充到每个item。如果这个填充的信息是其他的,就是用到其他数据结构了,如 List<Map<String, Object>> mData; 来保存信息。具体可以参考:[http://mobile.51cto.com/android-254823.htm](http://mobile.51cto.com/android-254823.htm)
[http://blog.csdn.net/a859522265/article/details/8204646](http://blog.csdn.net/a859522265/article/details/8204646) [http://www.linuxidc.com/Linux/2011-11/47179p2.htm](http://www.linuxidc.com/Linux/2011-11/47179p2.htm) [http://blog.sina.com.cn/s/blog_65570a20010108lp.html](http://blog.sina.com.cn/s/blog_65570a20010108lp.html) [http://bbs.csdn.net/topics/330062289](http://bbs.csdn.net/topics/330062289) 我主要参考的第一篇。
**另外,在数据库里怎么获得Cursor呢?**
cursor = mPalmDB.select();
select()函数的封装是:
~~~
public Cursor select(){
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, null, null, null, null, null, null);
return cursor;
}
~~~
这个可以封装在数据库类里,也可以写到函数里。
源码连同数据库操作部分改日再提供哈!
效果图:
1,主界面
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21aedfbe8.jpg)
2,在数据库里添加两个数据后:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21af08fbf.jpg)
3,点击上面的“选定”按键,切换到另外一个界面
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21af1f649.jpg)
4,选中这两个条目:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21af2e65f.jpg)
源码下载:[http://download.csdn.net/detail/yanzi1225627/5226894](http://download.csdn.net/detail/yanzi1225627/5226894)
欢迎android爱好者加群248217350 备注:yanzi
----------------------------本文系原创,转载请注明作者:yanzi1225627
setContentView切换页面(无需每次都findViewById)—–二
最后更新于:2022-04-01 07:24:18
[http://blog.csdn.net/yanzi1225627/article/details/7802402](http://blog.csdn.net/yanzi1225627/article/details/7802402)这里是我以前写的总结,这次要用到,可奇怪的是,不管咋弄,从界面A切换到界面B可以,从B到A也可以。再次从A到B死活都不行了。最终的原因在Jum2B 这个函数里,我用了setContentView(R.layout.B)的方法,而不是实例化出来的View BView参数。这里对这个问题,再做一次总结:
假设两个界面A和B,A里有Button为btn1, B里有Button为btn2. 启动时是界面A,然后按按键btn1和btn2可以切换到另外一个界面里。
1,这两个Button要弄成全局变量,如果在onCreate函数里弄成final 应该也行。
2,在全局变量里要定义两个View AView = null, View BView = null, 然后在onCreate里用inflater实例化一下。
3,之后弄两个函数Jump2B, Jump2A,这两个函数里setContentView传进去的参数是上面定义的**View**参数。两个View 必须都是。然后findViewById的时候,调用(AView).findViewById()这种方法。
4,在onCreate里设置btn1的监听,调用Jump2B方法,在Jump2B方法里,实例化出来btn2, 并设置监听。增加一个flag,只需设置监听一次就可以了。
**注意:要点是setContentView传进去的参数是用inflater实例化的View, 在Button 的实例化时调用(AView).findViewById() 。**
Android摄像头开发:实时摄像头视频预览帧的编码问题(二)
最后更新于:2022-04-01 07:24:16
### [Android开发:实时处理摄像头预览帧视频------浅析PreviewCallback,onPreviewFrame,AsyncTask的综合应用 ](http://blog.csdn.net/yanzi1225627/article/details/8605061)这里将大致框架介绍了,但很多人对onPreviewFrame()里的处理提出质疑。认为下面的转换是多余的:
~~~
final YuvImage image = new YuvImage(mData, ImageFormat.NV21, w, h, null);
ByteArrayOutputStream os = new ByteArrayOutputStream(mData.length);
if(!image.compressToJpeg(new Rect(0, 0, w, h), 100, os)){
return null;
}
byte[] tmp = os.toByteArray();
Bitmap bmp = BitmapFactory.decodeByteArray(tmp, 0,tmp.length);
~~~
###
因为这个mData是byte[ ]格式,转换流程是:byte[ ]---YuvImage----ByteArrayOutputStream---byte[ ]-----Bitmap。乍一看这个转换还真是多余了。看看看goolge的api:
~~~
public abstract void onPreviewFrame (byte[] data, Camera camera)
Added in API level 1
Called as preview frames are displayed. This callback is invoked on the event thread open(int) was called from.
If using the YV12 format, refer to the equations in setPreviewFormat(int) for the arrangement of the pixel data in the preview callback buffers.
Parameters
data the contents of the preview frame in the format defined by ImageFormat, which can be queried with getPreviewFormat(). If setPreviewFormat(int) is never called, the default will be the YCbCr_420_SP (NV21) format.
camera the Camera service object.
~~~
**大致意思是:可以用`[getPreviewFormat()](http://developer.android.com/reference/android/hardware/Camera.Parameters.html#getPreviewFormat())查询`支持的预览帧格式。如果`[setPreviewFormat(INT)](http://developer.android.com/reference/android/hardware/Camera.Parameters.html#setPreviewFormat(int))` 从未被调用,默认将使用YCbCr_420_SP的格式(NV21)。**
**setPreviewFormat里,它又说:**
~~~
public void setPreviewFormat (int pixel_format)
Added in API level 1
Sets the image format for preview pictures.
If this is never called, the default format will be NV21, which uses the NV21 encoding format.
Use getSupportedPreviewFormats() to get a list of the available preview formats.
It is strongly recommended that either NV21 or YV12 is used, since they are supported by all camera devices.
For YV12, the image buffer that is received is not necessarily tightly packed, as there may be padding at the end of each row of pixel data, as described in YV12. For camera callback data, it can be assumed that the stride of the Y and UV data is the smallest possible that meets the alignment requirements. That is, if the preview size is width x height, then the following equations describe the buffer index for the beginning of row y for the Y plane and row c for the U and V planes:
yStride = (int) ceil(width / 16.0) * 16;
uvStride = (int) ceil( (yStride / 2) / 16.0) * 16;
ySize = yStride * height;
uvSize = uvStride * height / 2;
yRowIndex = yStride * y;
uRowIndex = ySize + uvSize + uvStride * c;
vRowIndex = ySize + uvStride * c;
size = ySize + uvSize * 2;
~~~
强烈建议使用NV21格式和YV21格式,而默认情况下是NV21格式,也就是YUV420SP的。因此不经过转换,直接用BitmapFactory解析是不能成功的。事实也是如此。直接解析mData将会得到如下的错误:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21aec03de.jpg)
另外下面也提到NV21是通用的。
#### getSupportedPreviewFormats ()
Added in [API level 5](http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels)
Gets the supported preview formats. `[NV21](http://developer.android.com/reference/android/graphics/ImageFormat.html#NV21)` is always supported. `[YV12](http://developer.android.com/reference/android/graphics/ImageFormat.html#YV12)` is always supported since API level 12.
#####
如果嫌YuvImage进行压缩解析的慢,只能自己写转换函数了,网上常见的有三种:
**一:这里只是一个编码框架**
参考这里:Android 实时视频采集—Camera预览采集
~~~
// 【获取视频预览帧的接口】
mJpegPreviewCallback = new Camera.PreviewCallback()
{
@Override
public void onPreviewFrame(byte[] data, Camera camera)
{
//传递进来的data,默认是YUV420SP的
// TODO Auto-generated method stub
try
{
Log.i(TAG, "going into onPreviewFrame");
//mYUV420sp = data; // 获取原生的YUV420SP数据
YUVIMGLEN = data.length;
// 拷贝原生yuv420sp数据
mYuvBufferlock.acquire();
System.arraycopy(data, 0, mYUV420SPSendBuffer, 0, data.length);
//System.arraycopy(data, 0, mWrtieBuffer, 0, data.length);
mYuvBufferlock.release();
// 开启编码线程,如开启PEG编码方式线程
mSendThread1.start();
} catch (Exception e)
{
Log.v("System.out", e.toString());
}// endtry
}// endonPriview
};
~~~
**二、下面是将yuv420sp转成rgb参考这里:**[**android视频采集
**](http://yueguc.iteye.com/blog/820815)
~~~
private void updateIM() {
try {
// 解析YUV成RGB格式
decodeYUV420SP(byteArray, yuv420sp, width, height);
DataBuffer dataBuffer = new DataBufferByte(byteArray, numBands);
WritableRaster wr = Raster.createWritableRaster(sampleModel,
dataBuffer, new Point(0, 0));
im = new BufferedImage(cm, wr, false, null);
} catch (Exception ex) {
ex.printStackTrace();
}
}
private static void decodeYUV420SP(byte[] rgbBuf, byte[] yuv420sp,
int width, int height) {
final int frameSize = width * height;
if (rgbBuf == null)
throw new NullPointerException("buffer 'rgbBuf' is null");
if (rgbBuf.length < frameSize * 3)
throw new IllegalArgumentException("buffer 'rgbBuf' size "
+ rgbBuf.length + " < minimum " + frameSize * 3);
if (yuv420sp == null)
throw new NullPointerException("buffer 'yuv420sp' is null");
if (yuv420sp.length < frameSize * 3 / 2)
throw new IllegalArgumentException("buffer 'yuv420sp' size "
+ yuv420sp.length + " < minimum " + frameSize * 3 / 2);
int i = 0, y = 0;
int uvp = 0, u = 0, v = 0;
int y1192 = 0, r = 0, g = 0, b = 0;
for (int j = 0, yp = 0; j < height; j++) {
uvp = frameSize + (j >> 1) * width;
u = 0;
v = 0;
for (i = 0; i < width; i++, yp++) {
y = (0xff & ((int) yuv420sp[yp])) - 16;
if (y < 0)
y = 0;
if ((i & 1) == 0) {
v = (0xff & yuv420sp[uvp++]) - 128;
u = (0xff & yuv420sp[uvp++]) - 128;
}
y1192 = 1192 * y;
r = (y1192 + 1634 * v);
g = (y1192 - 833 * v - 400 * u);
b = (y1192 + 2066 * u);
if (r < 0)
r = 0;
else if (r > 262143)
r = 262143;
if (g < 0)
g = 0;
else if (g > 262143)
g = 262143;
if (b < 0)
b = 0;
else if (b > 262143)
b = 262143;
rgbBuf[yp * 3] = (byte) (r >> 10);
rgbBuf[yp * 3 + 1] = (byte) (g >> 10);
rgbBuf[yp * 3 + 2] = (byte) (b >> 10);
}
}
}
public static void main(String[] args) {
Frame f = new FlushMe();
}
}
~~~
**三、将YUV420SP转成YUV420格式**
** 参考这里:**[**Android如何实现边采集边上传**](http://blog.sina.com.cn/s/blog_51396f890102e07o.html)
~~~
private byte[] changeYUV420SP2P(byte[]data,int length){
int width = 176;
int height = 144;
byte[] str = new byte[length];
System.arraycopy(data, 0, str, 0,width*height);
int strIndex = width*height;
for(int i = width*height+1; i < length ;i+=2)
{
str[strIndex++] = data[i];
}
for(int i = width*height;i<length;i+=2)
{
str[strIndex++] = data[i];
}
return str;
}
~~~
至于怎么从YUV420SP中直接提取出Y分量进行后续检测,这个还要研究一番。有知道的大神多赐教。
----------------------------------------------------------------------------------------本文系原创,转载请注明作者:yanzi1225627
欢迎android爱好者加群248217350,备注:yanzi
Android(OpenCV) NDK开发: 0xdeadbaad(code=1)错误 及 关闭armeabi和libnative_camera_r2.2.2.so的生成
最后更新于:2022-04-01 07:24:14
一、OpenCV移植到android之后,开发中遇到错误:android fatal signal 11(SIGSEGV) at 0xdeadbaad (code=1).很是纠结,参考[https://community.freescale.com/docs/DOC-93378](https://community.freescale.com/docs/DOC-93378) [http://www.linkedin.com/groups/Application-crash-android-40-higher-86481.S.171547732](http://www.linkedin.com/groups/Application-crash-android-40-higher-86481.S.171547732),有的人说是程序中使用AsyncTask的问题,碰巧我程序里还真使用了。但经过仔细研究发现,如果报错:android fatal signal 11(SIGSEGV) at 0xdeadbaad (code=1).最先排查的还是本地C/C++代码的错误。 原来是opencv运算时矩阵维数错误的原因,导致程序奔溃。
二、原先Application.mk文件里APP_ABI=armeabi armeabi-v7a,所以生成的libs文件夹里总有两套这种东西。[经查](http://blog.csdn.net/dxpqxb/article/details/7721156),armeabi是指的该so库用于Arm的通用CPU,而v7a的CPU支持硬件浮点运算。因此armeabi通用性强,但速度慢,而v7a能充分发挥v7a CPU的能力。华为[U9200](http://detail.zol.com.cn/310/309697/param.shtml)cpu型号是OMAP4660,是A9的。查了半天也没看出来究竟支不支持硬件浮点运算。将armeabi去掉之后,原来的程序大小4.23M直接缩小至2.62M。运行良好,看来瘦身很关键啊。
三、原先在ndk-build编译本地库时,总是看到有libnative_camera_r2.2.2.so和一个r2.3.3.so生成,如下。奇怪的是,我在本地代码里根本没有使用camera模块。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21aea9c0a.jpg)
借鉴这里[http://stackoverflow.com/questions/9460424/opencv-on-android-duplicate-file-for-apk](http://stackoverflow.com/questions/9460424/opencv-on-android-duplicate-file-for-apk),在Application.mk文件里添加
~~~
OPENCV_CAMERA_MODULES:=off
~~~
果然,再次编译安装的时候两个camera的库不安装了!注意这句话写在Android.mk里无效。
Android摄像头开发:拍照后添加相框,融合相框和图片为一副 图片
最后更新于:2022-04-01 07:24:11
为了添加相框,可以新建一个bitmap,依此实例化一个canvas。然后再上面依次画上原图和相框。
在onPictureTaken()函数里,得到原始bitmap后,得到相框,然后调用融合函数。
Bitmap frame = BitmapFactory.decodeResource(getResources(), R.drawable.border);
Bitmap monBM = montageBitmap(frame, sizeBitmap, 200, 200);
~~~
/*将像框和图片进行融合,返回一个Bitmap*/
public Bitmap montageBitmap(Bitmap frame, Bitmap src, int x, int y){
int w = src.getWidth();
int h = src.getHeight();
Bitmap sizeFrame = Bitmap.createScaledBitmap(frame, w, h, true);
Bitmap newBM = Bitmap.createBitmap(w, h, Config.ARGB_8888);
Canvas canvas = new Canvas(newBM);
canvas.drawBitmap(src, x, y, null);
canvas.drawBitmap(sizeFrame, 0, 0, null);
return newBM;
}
~~~
程序中frame代表相框,src代表原图,大小为600*800.首先将相框的大小缩放到600*800,然后实例化一个canvas。记住先画原图。这里面有个x、y坐标。
这里是这个api的注释:
~~~
public void drawBitmap (Bitmap bitmap, float left, float top, Paint paint)
Added in API level 1
Draw the specified bitmap, with its top/left corner at (x,y), using the specified paint, transformed by the current matrix.
Note: if the paint contains a maskfilter that generates a mask which extends beyond the bitmap's original width/height (e.g. BlurMaskFilter), then the bitmap will be drawn as if it were in a Shader with CLAMP mode. Thus the color outside of the original width/height will be the edge color replicated.
If the bitmap and canvas have different densities, this function will take care of automatically scaling the bitmap to draw at the same density as the canvas.
Parameters
bitmap The bitmap to be drawn
left The position of the left side of the bitmap being drawn
top The position of the top side of the bitmap being drawn
paint The paint used to draw the bitmap (may be null)
~~~
看上面的解释,貌似不清楚这个x y坐标到底是谁的坐标,是原图的 还是canvas的?而且如果要画的图超过canvas的大小怎么办?经过实际测试,参考[这里](http://book.51cto.com/art/201204/328278.htm),这个x、y坐标是指canvas上的,也就是以canvas上的点(x,y)为顶点,来画图bitmap。如果bitmap的大小超过canvas的大小,就不显示了。下面两组测试图片可以清楚看到。
第一组测试照片(x,y)=(20, 20):
原图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21ada0a5f.jpg)
原图+相框:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21aded9a5.jpg)
第二组(x,y)=(200, 200):
原图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21ae3ae92.jpg)
原图+相框:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21ae78567.jpg)
可以看到,当传进去的坐标较小时看不出来啥差别。事实上,如果将两个坐标都设为(0,0),看到的是两个同样大小的照片层叠的效果。这就看对相框如何定义了。如果要求不遮挡原图,则需要把原图缩放到rect大小,这个rect是指相框里面的空白(透明)部分大小。然后从canvas的透明部分的左上顶点开始画缩放后的原图。
[http://blog.csdn.net/lgl125/article/details/7866930](http://blog.csdn.net/lgl125/article/details/7866930)这个链接是给原图加边框的,但不是相框!可以参考。
-----------------------------------------------------------------本文系原创,转载请注明作者:yanzi1225627
Android开发:实时处理摄像头预览帧视频——浅析PreviewCallback,onPreviewFrame,AsyncTask的综合应用
最后更新于:2022-04-01 07:24:09
很多时候,android摄像头模块不仅预览,拍照这么简单,而是需要在预览视频的时候,能够做出一些检测,比如最常见的人脸检测。在未按下拍照按钮前,就检测出人脸然后矩形框标示出来,再按拍照。那么如何获得预览帧视频么?
只需要在Activity里继承PreviewCallback这个接口就行了。示例如下:
public class RectPhoto extends Activity implements SurfaceHolder.Callback, PreviewCallback{}。(注意这个SurfaceHolder.Callback是用来预览摄像头视频,参见[我的前贴](http://blog.csdn.net/yanzi1225627/article/details/8577756))。
继承这个方法后,会自动重载这个函数:public void onPreviewFrame(byte[] data, Camera camera) {}这个函数里的data就是实时预览帧视频。一旦程序调用PreviewCallback接口,就会自动调用onPreviewFrame这个函数。调用PreviewCallback的方法有三种,可以参考[这里](http://read.360buy.com/12142/576884.html),总共有三种方式调用这个回调。所谓回调就是当条件满足时,自动触发调用这个函数。分别是:.setPreviewCallback, setOneShotPreviewCallback, setPreviewCallbackWithBuffer, 我一般是使用第二种方式。
这里解释下,如果Activity继承了PreviewCallback这个接口,只需 Camera.setOneShotPreviewCallback(this);就可以了。程序会自动调用主类Activity里的onPreviewFrame函数。如果Camera.setOneShotPreviewCallback()这个函数是在主类Activity里的内部类如class A里面,里面的参数应写为Camera.setOneShotPreviewCallback(YourActivity.this)。当然这里,也可以定义一个变量,如Camera.PreviewCallback mPreviewCallback,在调用的时候用Camera.setOneShotPreviewCallback(mPreviewCallback)来完成。相信很多人都熟悉这点,就不罗嗦了。
按理说只要在onPreviewFrame()这个函数里写你的处理程序就可以了。当通常不这么做,因为处理实时预览帧视频的算法可能比较复杂,这就需要借助AsyncTask开启一个线程在后台处理数据。这里假设我们定义一个FaceTask来进行人脸检测,可以这样写:
/*自定义的FaceTask类,开启一个线程分析数据*/
private class FaceTask extends AsyncTask<Void, Void, Void>{
private byte[] mData;
//构造函数
PalmTask(byte[] data){
this.mData = data;
}
@Override
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
Size size = myCamera.getParameters().getPreviewSize(); //获取预览大小
final int w = size.width; //宽度
final int h = size.height;
final YuvImage image = new YuvImage(mData, ImageFormat.NV21, w, h, null);
ByteArrayOutputStream os = new ByteArrayOutputStream(mData.length);
if(!image.compressToJpeg(new Rect(0, 0, w, h), 100, os)){
return null;
}
byte[] tmp = os.toByteArray();
**Bitmap bmp = BitmapFactory.decodeByteArray(tmp, 0,tmp.length); **
** doSomethingNeeded(bmp); //自己定义的实时分析预览帧视频的算法**
return null;
}
}
注意上面的bmp就是Bitmap格式的实时预览帧数据。**doSomethingNeeded(bmp) 就是你要对预览帧视频进行的处理,可以是检测人脸或其他,如分析有无火灾。或者是进行传输。 另外,这里是通过YuvImage和ImageFormat.NV21来解析数据的。在华为u9200上,android4.0.3的系统运行良好。不同手机上支持的格式可能有所不同。网上也有自己写算法进行转化的,需要的可以自己找,但这里如果支持这个格式就不用自己写转换算法了。 **
onPreviewFrame()里可以这样写:
/*获取预览帧视频*/
public void onPreviewFrame(byte[] data, Camera camera) {
// TODO Auto-generated method stub
if(null != mFaceTask){
switch(mFaceTask.getStatus()){
case RUNNING:
return;
case PENDING:
mFaceTask.cancel(false);
break;
}
}
mFaceTask = new PalmTask(data);
mFaceTask.execute((Void)null);
}
上面的mFaceTask是一个全局变量。通过onPreviewFrame,AsyncTask的综合应用,让复杂的处理算法执行在后台,也就是doInBackground这里,是不是比较绿色?
接下来就是什么时候触发onPreviewFrame()这个函数里,可以是按一个按键触发一次,就在按键的监听里写上 myCamera.setOneShotPreviewCallback(RectPhoto.this);便会自动触发一次。有人说想先聚焦,然后再分析预览帧。就在onAutofocus里的回调写。如下:
//自动聚焦变量回调
myAutoFocusCallback = new AutoFocusCallback() {
public void onAutoFocus(boolean success, Camera camera) {
// TODO Auto-generated method stub
if(success)//success表示对焦成功
{
Log.i(tag, "myAutoFocusCallback: success...");
myCamera.setOneShotPreviewCallback(RectPhoto.this);
}
else
{
//未对焦成功
Log.i(tag, "myAutoFocusCallback: 失败了...");
** //这里也可以加上myCamera.autoFocus(myAutoFocusCallback),如果聚焦失败就再次启动聚焦。**
}
}
};
大多数时候,希望程序自动每隔多长时间,自动进行一次检测预览帧。这也好办,实施如下:
~~~
class ScanThread implements Runnable{
public void run() {
// TODO Auto-generated method stub
while(!Thread.currentThread().isInterrupted()){
try {
if(null != myCamera && isPreview)
{
//myCamera.autoFocus(myAutoFocusCallback);
myCamera.setOneShotPreviewCallback(RectPhoto.this);
Log.i(tag, "setOneShotPreview...");
}
Thread.sleep(1500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
}
~~~
在onCreate里new Thread(new ScanThread()).start()开启扫描线程。如果想手动触发中止这种扫描活动,可以在ScanThread里的while循环里设置标志位,具体可看我[以前的博文](http://blog.csdn.net/yanzi1225627/article/details/8581200)。
最后提醒的是,如果程序中加入了previewCallback,在surfaceDestroy释放camera的时候,最好执行myCamera.setOneShotPreviewCallback(null); 或者myCamera.setPreviewCallback(null);中止这种回调,然后再释放camera更安全。否则可能会报错。
欢迎android爱好者加群248217350,备注:yanzi
-----------------------------------------------------------------本文系原创,转载请注明作者:yanzi1225627
Android上掌纹识别第一步:基于OpenCV的6种肤色分割 源码和效果图
最后更新于:2022-04-01 07:24:07
六种方法分别是:基于RGB分割,基于RG同道的分割,ycrcb+otsu(ostu可以参考http://blog.csdn.net/onezeros/article/details/6136770,
http://wenku.baidu.com/view/05c47e03bed5b9f3f90f1ce4.html),YCrCb空间,YUV空间,HSV空间。下一步就是通过JNI将这些检测移植到android上,最终目标是实现Android智能手机利用掌纹开关机。
环境是在qt下,.pro文件里增加如下代码:
~~~
INCLUDEPATH += /usr/include/opencv
LIBS += /usr/lib/libcv.so \
/usr/lib/libcvaux.so \
/usr/lib/libcxcore.so \
/usr/lib/libhighgui.so \
/usr/lib/libml.so
~~~
请看源码:
~~~
#include <iostream>
#include "cv.h"
#include "highgui.h"
void SkinRGB(IplImage* rgb,IplImage* _dst);
void cvSkinRG(IplImage* rgb,IplImage* gray);
void cvThresholdOtsu(IplImage* src, IplImage* dst);
void cvSkinOtsu(IplImage* src, IplImage* dst);
void cvSkinYCbCr(IplImage* img, IplImage* mask);
void cvSkinYUV(IplImage* src,IplImage* dst);
void cvSkinHSV(IplImage* src,IplImage* dst);
using namespace std;
// skin region location using rgb limitation
int main()
{
IplImage *srcImg = cvLoadImage("/home/yan/download/testPalm4.jpg", 1);
IplImage *dstRGB = cvCreateImage(cvGetSize(srcImg), 8, 3);
IplImage *dstRG = cvCreateImage(cvGetSize(srcImg), 8, 1);
IplImage* dst_crotsu=cvCreateImage(cvGetSize(srcImg),8,1);
IplImage* dst_ycbcr=cvCreateImage(cvGetSize(srcImg),8,1);
IplImage* dst_yuv=cvCreateImage(cvGetSize(srcImg),8,3);
IplImage* dst_hsv=cvCreateImage(cvGetSize(srcImg),8,3);
SkinRGB(srcImg, dstRGB);
cvSaveImage("/home/yan/download/1_dstRGB.jpg", dstRGB);
cvSkinRG(srcImg, dstRG);
cvSaveImage("/home/yan/download/2_dstRG.jpg", dstRG);
cvSkinOtsu(srcImg, dst_crotsu);
cvSaveImage("/home/yan/download/3_dst_crotsu.jpg", dst_crotsu);
cvSkinYCbCr(srcImg, dst_ycbcr);
cvSaveImage("/home/yan/download/4_dst_ycbcr.jpg", dst_ycbcr);
cvSkinYUV(srcImg, dst_yuv);
cvSaveImage("/home/yan/download/5_dst_yuv.jpg", dst_yuv);
cvSkinHSV(srcImg, dst_hsv);
cvSaveImage("/home/yan/download/6_dst_hsv.jpg", dst_hsv);
cvNamedWindow("srcImg", 1);
cvShowImage("srcImg", srcImg);
cvNamedWindow("dstRGB", 1);
cvShowImage("dstRGB", dstRGB);
cvNamedWindow("dstRG", 1);
cvShowImage("dstRG", dstRG);
cvNamedWindow("dstcrotsu", 1);
cvShowImage("dstcrotsu", dst_crotsu);
cvNamedWindow("dst_ycbcr", 1);
cvShowImage("dst_ycbcr", dst_ycbcr);
cvNamedWindow("dst_yuv", 1);
cvShowImage("dst_yuv", dst_yuv);
cvNamedWindow("dst_hsv", 1);
cvShowImage("dst_hsv", dst_hsv);
cvWaitKey(0);
cout << "Hello World!" << endl;
return 0;
}
void SkinRGB(IplImage* rgb,IplImage* _dst)
{
cout<<"111"<<endl;
assert(rgb->nChannels==3&& _dst->nChannels==3);
static const int R=2;
static const int G=1;
static const int B=0;
IplImage* dst=cvCreateImage(cvGetSize(_dst),8,3);
cvZero(dst);
for (int h=0;h<rgb->height;h++) {
unsigned char* prgb=(unsigned char*)rgb->imageData+h*rgb->widthStep;
unsigned char* pdst=(unsigned char*)dst->imageData+h*dst->widthStep;
for (int w=0;w<rgb->width;w++) {
if ((prgb[R]>95 && prgb[G]>40 && prgb[B]>20 &&
prgb[R]-prgb[B]>15 && prgb[R]-prgb[G]>15/*&&
!(prgb[R]>170&&prgb[G]>170&&prgb[B]>170)*/)||//uniform illumination
(prgb[R]>200 && prgb[G]>210 && prgb[B]>170 &&
abs(prgb[R]-prgb[B])<=15 && prgb[R]>prgb[B]&& prgb[G]>prgb[B])//lateral illumination
) {
memcpy(pdst,prgb,3);
}
prgb+=3;
pdst+=3;
}
}
cvCopyImage(dst,_dst);
cvReleaseImage(&dst);
}
void cvSkinRG(IplImage* rgb,IplImage* gray)
{
assert(rgb->nChannels==3&&gray->nChannels==1);
const int R=2;
const int G=1;
const int B=0;
double Aup=-1.8423;
double Bup=1.5294;
double Cup=0.0422;
double Adown=-0.7279;
double Bdown=0.6066;
double Cdown=0.1766;
for (int h=0; h<rgb->height; h++)
{
unsigned char* pGray=(unsigned char*)gray->imageData+h*gray->widthStep;
unsigned char* pRGB=(unsigned char* )rgb->imageData+h*rgb->widthStep;
for (int w=0; w<rgb->width; w++)
{
int s=pRGB[R]+pRGB[G]+pRGB[B];
double r=(double)pRGB[R]/s;
double g=(double)pRGB[G]/s;
double Gup=Aup*r*r+Bup*r+Cup;
double Gdown=Adown*r*r+Bdown*r+Cdown;
double Wr=(r-0.33)*(r-0.33)+(g-0.33)*(g-0.33);
if (g<Gup && g>Gdown && Wr>0.004)
{
*pGray=255;
}
else
{
*pGray=0;
}
pGray++;
pRGB+=3;
}
}
}
void cvThresholdOtsu(IplImage* src, IplImage* dst)
{
int height=src->height;
int width=src->width;
//histogram
float histogram[256]= {0};
for(int i=0; i<height; i++)
{
unsigned char* p=(unsigned char*)src->imageData+src->widthStep*i;
for(int j=0; j<width; j++)
{
histogram[*p++]++;
}
}
//normalize histogram
int size=height*width;
for(int i=0; i<256; i++)
{
histogram[i]=histogram[i]/size;
}
//average pixel value
float avgValue=0;
for(int i=0; i<256; i++)
{
avgValue+=i*histogram[i];
}
int threshold;
float maxVariance=0;
float w=0,u=0;
for(int i=0; i<256; i++)
{
w+=histogram[i];
u+=i*histogram[i];
float t=avgValue*w-u;
float variance=t*t/(w*(1-w));
if(variance>maxVariance)
{
maxVariance=variance;
threshold=i;
}
}
cvThreshold(src,dst,threshold,255,CV_THRESH_BINARY);
}
void cvSkinOtsu(IplImage* src, IplImage* dst)
{
assert(dst->nChannels==1&& src->nChannels==3);
IplImage* ycrcb=cvCreateImage(cvGetSize(src),8,3);
IplImage* cr=cvCreateImage(cvGetSize(src),8,1);
cvCvtColor(src,ycrcb,CV_BGR2YCrCb);
cvSplit(ycrcb,0,cr,0,0);
cvThresholdOtsu(cr,cr);
cvCopyImage(cr,dst);
cvReleaseImage(&cr);
cvReleaseImage(&ycrcb);
}
void cvSkinYCbCr(IplImage* img, IplImage* mask)
{
CvSize imageSize = cvSize(img->width, img->height);
IplImage *imgY = cvCreateImage(imageSize, IPL_DEPTH_8U, 1);
IplImage *imgCr = cvCreateImage(imageSize, IPL_DEPTH_8U, 1);
IplImage *imgCb = cvCreateImage(imageSize, IPL_DEPTH_8U, 1);
IplImage *imgYCrCb = cvCreateImage(imageSize, img->depth, img->nChannels);
cvCvtColor(img,imgYCrCb,CV_BGR2YCrCb);
cvSplit(imgYCrCb, imgY, imgCr, imgCb, 0);
int y, cr, cb, l, x1, y1, value;
unsigned char *pY, *pCr, *pCb, *pMask;
pY = (unsigned char *)imgY->imageData;
pCr = (unsigned char *)imgCr->imageData;
pCb = (unsigned char *)imgCb->imageData;
pMask = (unsigned char *)mask->imageData;
cvSetZero(mask);
l = img->height * img->width;
for (int i = 0; i < l; i++){
y = *pY;
cr = *pCr;
cb = *pCb;
cb -= 109;
cr -= 152
;
x1 = (819*cr-614*cb)/32 + 51;
y1 = (819*cr+614*cb)/32 + 77;
x1 = x1*41/1024;
y1 = y1*73/1024;
value = x1*x1+y1*y1;
if(y<100) (*pMask)=(value<700) ? 255:0;
else (*pMask)=(value<850)? 255:0;
pY++;
pCr++;
pCb++;
pMask++;
}
cvReleaseImage(&imgY);
cvReleaseImage(&imgCr);
cvReleaseImage(&imgCb);
cvReleaseImage(&imgYCrCb);
}
void cvSkinYUV(IplImage* src,IplImage* dst)
{
IplImage* ycrcb=cvCreateImage(cvGetSize(src),8,3);
//IplImage* cr=cvCreateImage(cvGetSize(src),8,1);
//IplImage* cb=cvCreateImage(cvGetSize(src),8,1);
cvCvtColor(src,ycrcb,CV_BGR2YCrCb);
//cvSplit(ycrcb,0,cr,cb,0);
static const int Cb=2;
static const int Cr=1;
static const int Y=0;
//IplImage* dst=cvCreateImage(cvGetSize(_dst),8,3);
cvZero(dst);
for (int h=0; h<src->height; h++)
{
unsigned char* pycrcb=(unsigned char*)ycrcb->imageData+h*ycrcb->widthStep;
unsigned char* psrc=(unsigned char*)src->imageData+h*src->widthStep;
unsigned char* pdst=(unsigned char*)dst->imageData+h*dst->widthStep;
for (int w=0; w<src->width; w++)
{
if (pycrcb[Cr]>=133&&pycrcb[Cr]<=173&&pycrcb[Cb]>=77&&pycrcb[Cb]<=127)
{
memcpy(pdst,psrc,3);
}
pycrcb+=3;
psrc+=3;
pdst+=3;
}
}
//cvCopyImage(dst,_dst);
//cvReleaseImage(&dst);
}
void cvSkinHSV(IplImage* src,IplImage* dst)
{
IplImage* hsv=cvCreateImage(cvGetSize(src),8,3);
//IplImage* cr=cvCreateImage(cvGetSize(src),8,1);
//IplImage* cb=cvCreateImage(cvGetSize(src),8,1);
cvCvtColor(src,hsv,CV_BGR2HSV);
//cvSplit(ycrcb,0,cr,cb,0);
static const int V=2;
static const int S=1;
static const int H=0;
//IplImage* dst=cvCreateImage(cvGetSize(_dst),8,3);
cvZero(dst);
for (int h=0; h<src->height; h++)
{
unsigned char* phsv=(unsigned char*)hsv->imageData+h*hsv->widthStep;
unsigned char* psrc=(unsigned char*)src->imageData+h*src->widthStep;
unsigned char* pdst=(unsigned char*)dst->imageData+h*dst->widthStep;
for (int w=0; w<src->width; w++)
{
if (phsv[H]>=7&&phsv[H]<=29)
{
memcpy(pdst,psrc,3);
}
phsv+=3;
psrc+=3;
pdst+=3;
}
}
//cvCopyImage(dst,_dst);
//cvReleaseImage(&dst);
}
~~~
下面是效果图:
测试图片:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21ac93ccb.jpg)
下图的贴图依次对应上面的六种方法:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21acc03a8.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21acdd39c.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21ad0652e.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21ad18287.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21ad38c05.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21ad6923c.jpg)
从上面的结果对比图中可以清晰看的,ycrcb+ostu的效果无疑是最好的。其次是rgb和yuv方法。这个图片效果之所以这么好是因为测试图片拍摄的时候背景为白色。然后,遗憾的是,当背景色不纯的时候,比如有红也有黑,效果就很不理想了。实验发现,当背景为纯色,且是白色或黑色时,效果最好。
参考:
http://blog.sina.com.cn/s/blog_9ce5a1b501017otq.html
http://blog.csdn.net/scyscyao/article/details/5468577
http://wenku.baidu.com/view/05c47e03bed5b9f3f90f1ce4.html
http://blog.csdn.net/onezeros/article/details/6136770
--------------------------本掌纹是作者自己的,转载请注明作者yanzi1225627
Android开发:ImageView上绘制旋转圆环(透明度不同的旋转圆环,利用canvas.drawArc实现)
最后更新于:2022-04-01 07:24:05
上文已经绘制了圆环,但仔细分析就知,如果只需要圆环的话,那么只绘制圆环就可以,不用画内圆和外圆了。事实证明也是如此。
但是要做成和下面的圆环透明度不一的效果上面的方法还是达不中:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21ac36948.jpg)
用drawCircle不中了,经查android提供了绘制圆弧的函数drawArc,参考http://zhidao.baidu.com/question/469977150.html,也可以看这里http://blog.chinaunix.net/uid-23392298-id-3345789.html
~~~
canvas.drawArc(new RectF(0, 0, 128, 128), 0, 360, true, new Paint(
Paint.ANTI_ALIAS_FLAG));
参数1:圆的范围大小
参数2:起始角度
参数3:圆心角角度,360为圆,180为半圆
参数4:中心
参数5:画笔Paint,可以设置画线or填充,设置颜色,设置线的粗细等等第四个参数
~~~
最关键的是第一个参数RectF,在什么地方绘制圆弧就是由这个矩形的位置确定的。根据**[上文](http://blog.csdn.net/yanzi1225627/article/details/8581840)**,这个RectF应该是内切圆弧的外圆(尽管没画,但还是有)。所以其左上点及右下点坐标为:
RectF rect2 = new RectF(center-(innerCircle + 1 +ringWidth/2),center-(innerCircle + 1 +ringWidth/2), center+(innerCircle + 1 +ringWidth/2), center+(innerCircle + 1 +ringWidth/2));
为了绘制出透明度不同的圆环分两部来绘制:
this.paint.setARGB(200, 127, 255, 212);
this.paint.setStrokeWidth(ringWidth);
//绘制不透明部分
canvas.drawArc(rect2, 180+startAngle, 90, false, paint);
canvas.drawArc(rect2, 0+startAngle, 90, false, paint);
//绘制透明部分
this.paint.setARGB(30, 127, 255, 212);
canvas.drawArc(rect2, 90+startAngle, 90, false, paint);
canvas.drawArc(rect2, 270+startAngle, 90, false, paint);
上面的代码当startAngle = 0时,绘制的是一个静态的透明度交替的圆弧。接着要让它转起来。增加代码:
startAngle+=10;
if(startAngle == 180)
startAngle = 0;
事实上后两句也可以不增加,仿照[前文SurfaceView绘制旋转动画的例子](http://blog.csdn.net/yanzi1225627/article/details/8581200)用这种求余的思想,(rotate += 48) % 360,把上面角度也弄个%360,也是可以的。
剩下的事就是让这个东西循环执行了。在super.onDraw(canvas);这句代码后面加 invalidate();就可以了!透明圆环就转起来了。
Android开发:SurfaceView上新建线程绘制旋转图片 及 刷新特定区域(脏矩形)
最后更新于:2022-04-01 07:24:02
何为脏矩形?比如surfaceview上绘制旋转图片的时候,只刷新一个特定的矩形区域就可以了。这个矩形区域就是脏矩形。在surfaceview上 canvas = holder.lockCanvas(new Rect(0, 0, 130, 130)); //获取画布 像这种带rect参数的就是脏矩形的刷新。
请看源码:
~~~
package yan.guoqi.rectphoto;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class DrawSurfaceView extends SurfaceView implements SurfaceHolder.Callback{
private static final String tag2 = "DrawSV";
private boolean runFlag = true;
protected SurfaceHolder holder;
private Bitmap rotateImg;
private Thread myThread;
public DrawSurfaceView(Context context, AttributeSet attrs)
//构造函数
{
super(context, attrs);
// TODO Auto-generated constructor stub
rotateImg = BitmapFactory.decodeResource(getResources(), R.drawable.rotate_circle);
rotateImg = Bitmap.createScaledBitmap(rotateImg, 100, 100, true);
holder = this.getHolder();
holder.addCallback(this);
holder.setFormat(PixelFormat.TRANSPARENT); //顶层绘制SurfaceView设成透明
this.setZOrderOnTop(true);
myThread = new Thread(new MyThread());
}
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub
Log.v(tag2, "DrawSV:surfaceChanged...");
}
public void surfaceCreated(SurfaceHolder arg0) {
// TODO Auto-generated method stub
Log.v(tag2, "DrawSV:surfaceCreated...");
//启动自定义线程
myThread.start();
}
public void surfaceDestroyed(SurfaceHolder arg0) {
// TODO Auto-generated method stub
Log.v(tag2, "DrawSV:surfaceDestroyed...");
//终止自定义线程
runFlag = false;
myThread.interrupt();
}
/*自定义线程*/
class MyThread implements Runnable{
public void run() {
// TODO Auto-generated method stub
Canvas canvas = null;
int rotate = 0;
while(runFlag){
try {
canvas = holder.lockCanvas(new Rect(0, 0, 130, 130)); //获取画布
Paint paint = new Paint();
//canvas.drawBitmap(rotateImg, 0, 0, paint); //绘制旋转的背景
//创建矩阵控制图片旋转和平移
Matrix matrix = new Matrix();
//设置旋转角度
matrix.postRotate((rotate += 48) % 360,
rotateImg.getWidth() / 2, rotateImg.getHeight() / 2);
//设置左边距和上边距
matrix.postTranslate(0, 0);
//绘制旋转图片
canvas.drawBitmap(rotateImg, matrix, paint);
//休眠控制最大帧率为每秒3绘制30次
Thread.sleep(30);
holder.unlockCanvasAndPost(canvas);//解锁画布,提交画好的图像
} catch (Exception e) {
// TODO: handle exception
Log.v(tag2, "DrawSurfaceView:绘制失败...");
}
}
}
}
}
~~~
**需要注意的几点:**
第一,就是如何安全中止一个线程。这里通过设置标志位来实现。因为自带的stop()方法不安全,android本身都不推荐。关于如何安全中止线程可以参考这里:http://www.iteye.com/problems/67052 http://www.dewen.org/q/1957
第二,holder.unlockCanvasAndPost(canvas);//解锁画布,提交画好的图像 这句话必须和 canvas = holder.lockCanvas(new Rect(0, 0, 130, 130)); //获取画布 在一个{ }里。像这里的http://www.cnblogs.com/jqyp/archive/2012/01/01/2309692.html的做法在程序退出时会抛出异常。原因是线程中止的时候无法再解锁画布了。
第三,利用Matrix让图片旋转的核心代码:
~~~
//创建矩阵控制图片旋转和平移
Matrix matrix = new Matrix();
//设置旋转角度
matrix.postRotate((rotate += 48) % 360,
rotateImg.getWidth() / 2, rotateImg.getHeight() / 2);
//设置左边距和上边距
matrix.postTranslate(0, 0);
//绘制旋转图片
canvas.drawBitmap(rotateImg, matrix, paint);
//休眠控制最大帧率为每秒3绘制30次
Thread.sleep(30);
~~~
第四,之所以将此surfaceview设成顶层透明,原因是杂家想探索在底层surfaceview预览摄像头视频的时候,在顶层的surfaceview绘制一个旋转的图片动画。但遗憾的是,图片确实是旋转了,底层的摄像头预览也正常。但就是旋转的图片将预览画面挡住了。**唉,看来前文http://blog.csdn.net/yanzi1225627/article/details/8580034这里的360扫描二维码的界面不是通过这种思路实现的,抑或是 我旋转的图片不是透明的??? ****
Android摄像头:只拍摄SurfaceView预览界面特定区域内容(矩形框)—完整实现(原理:底层SurfaceView+上层绘制ImageView)
最后更新于:2022-04-01 07:24:00
**【后注:】下载代码的注意,我的手机是4.3寸的屏,华为U9200.如果不能运行的请修改参数。看[前文](http://blog.csdn.net/yanzi1225627/article/details/8577756)的第四条。Y的,省的说我传的代码不能用![发火](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21ac277db.gif)
**
最近一直在审视以前做过的东西,关于android摄像头预览,预览界面上呈现矩形框,在前文(
### [Android开发 摄像头SurfaceView预览 背景带矩形框 实现(原理:双surfaceview,顶层画矩形框,底层预览视频)](http://blog.csdn.net/yanzi1225627/article/details/7934710)
)----http://blog.csdn.net/yanzi1225627/article/details/7934710已经实现。最近发现上层绘制矩形框,用surfaceview有点大材小用了。SurfaceView绘制动画更合适,只绘制个矩形框用ImageView足够了。但有些时候必须要用SurfaceView来实现。比如360手机安全卫士扫描二维码的实现应该就是通过上下两层SurfaceView实现的(见下图)。上层SurfaceView用于显示那个可以旋转的扫描示意框,底层SurfaceView预览摄像头视频。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21ac36948.jpg)
废话不说了,稍候几天我会仿照上面360这个扫描二维码的界面做一个工程(结合PreviewCallback),公开出来。这次先谈用底层surfaceView+上层ImageView实现只拍摄矩形框中的图像。新建一个类继承ImageView,源码如下:
~~~
package yan.guoqi.rectphoto;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.ImageView;
public class DrawImageView extends ImageView{
public DrawImageView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
Paint paint = new Paint();
{
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(2.5f);//设置线宽
paint.setAlpha(100);
};
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
canvas.drawRect(new Rect(100, 200, 400, 500), paint);//绘制矩形
}
}
~~~
布局文件里与前文http://blog.csdn.net/yanzi1225627/article/details/8577756这里一样,只是在帧布局里加一个上面自定义的DrawImageView,整个布局文件示下:
~~~
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/BestWish"
tools:context=".RectPhoto" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<SurfaceView
android:id="@+id/previewSV"
android:layout_width="fill_parent"
android:layout_height="800px" />
<yan.guoqi.rectphoto.DrawImageView
android:id="@+id/drawIV"
android:layout_width="fill_parent"
android:layout_height="800px"
/>
</FrameLayout>
<ImageButton
android:id="@+id/photoImgBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/photo_img_btn"
android:layout_gravity="center" />
</LinearLayout>
~~~
在主程序文件里,onCreate()函数里设置底层SurfaceView为底层且透明(如果不设也可以,默认就是如此):
mPreviewSV.setZOrderOnTop(false);
mySurfaceHolder.setFormat(PixelFormat.TRANSPARENT);//translucent半透明 transparent透明
在主UI线程里的onCreate()函数里添加代码:
//绘制矩形的ImageView
mDrawIV = (yan.guoqi.rectphoto.DrawImageView)findViewById(R.id.drawIV);
mDrawIV.onDraw(new Canvas());
看上面的DrawImageView的函数里的onDraw,画的矩形是Rect(100, 200, 400, 500)。在onPictureTaken(byte[] data, Camera camera)函数里,先将图片旋转90度,大小成为宽×高(960×1280)。由于预览surfaceview的大小是宽×高(540×800),所以在onPictureTaken函数里将960×1280的图片缩放到540×800, 缩放相同大小后就可以用矩阵的坐标直接截取子图了。核心函数就是这两句:
//将960×1280缩放到540×800
** Bitmap sizeBitmap = Bitmap.createScaledBitmap(rotaBitmap, 540, 800, true);
Bitmap rectBitmap = Bitmap.createBitmap(sizeBitmap, 100, 200, 300, 300);//截取**
注意这个截取的函数参数和矩阵的坐标关系,分别是x轴 y轴起始坐标及 x轴宽度 y轴宽度。截取出来的图片大小应该是300×300. onPictureTaken()函数的源码如下:
~~~
public void onPictureTaken(byte[] data, Camera camera) {
// TODO Auto-generated method stub
Log.i(tag, "myJpegCallback:onPictureTaken...");
if(null != data){
mBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);//data是字节数据,将其解析成位图
myCamera.stopPreview();
isPreview = false;
}
//设置FOCUS_MODE_CONTINUOUS_VIDEO)之后,myParam.set("rotation", 90)失效。图片竟然不能旋转了,故这里要旋转下
Matrix matrix = new Matrix();
matrix.postRotate((float)90.0);
Bitmap rotaBitmap = Bitmap.createBitmap(mBitmap, 0, 0, mBitmap.getWidth(), mBitmap.getHeight(), matrix, false);
//旋转后rotaBitmap是960×1280.预览surfaview的大小是540×800
//将960×1280缩放到540×800
Bitmap sizeBitmap = Bitmap.createScaledBitmap(rotaBitmap, 540, 800, true);
Bitmap rectBitmap = Bitmap.createBitmap(sizeBitmap, 100, 200, 300, 300);//截取
//保存图片到sdcard
if(null != rectBitmap)
{
saveJpeg(rectBitmap);
}
//再次进入预览
myCamera.startPreview();
isPreview = true;
}
~~~
涉及到的其他函数如saveJpeg()参见前文:
### [2013新春奉送:Android摄像头开发完美demo---(循环聚焦,缩放大小,旋转picture,查询支持的picturesize, ImageButton按键效果)------------](http://blog.csdn.net/yanzi1225627/article/details/8577756)
http://blog.csdn.net/yanzi1225627/article/details/8577756 重复的东西我就不发了。
效果图如下所示:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21ac535f8.jpg)
点击拍照,查看保存后的图片如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21ac6d30e.jpg)
反思:
1,SurfaceView为啥 无论translucent半透明还是 transparent透明基本没啥区别?而且surfaceview的setAlpha函数不能用。
2,在这里surfaceview一定要在底层(默认如此),如果设成顶层会看不到红色矩形框。可以自己测试下。
3,最纠结的一点,第一副图片里的360扫描二维码的界面,底层的预览surfaceview是半透明的,底色是灰色的,只有中间的扫描矩形框是透明的,亮色。这一块究竟是怎么实现的??下午实验了n种方法愣是无济于事。我擦。。。如果有高人,希望能不吝指点下。 不过说实话,人家已经设计出来的产品界面看着就是好,不得不服阿。以后要多多模仿钻研这些成型产品的设计。
源码下载:[http://download.csdn.net/detail/yanzi1225627/5063105](http://download.csdn.net/detail/yanzi1225627/5063105)
欢迎android爱好者加群248217350,备注:yanzi
----------------------------------------本文系原创,转载请注明作者:yanzi1225627
如何设置ImageButton按键按下去后的 特效—-(如类似风车旋转的动画特效)
最后更新于:2022-04-01 07:23:58
android默认的Imagebutton按下去之后没有一点变化,实在让人受不了。这次在重新写摄像头相关的demo时,特意学习了这方面的内容。
**第一种方法:**
前文http://blog.csdn.net/yanzi1225627/article/details/7814392这是一种思路,可以用美图秀秀将同一个图片做成不同的灰度/色调效果,保存成同样大小的图片,然后利用http://blog.csdn.net/yanzi1225627/article/details/7814392这里设置成点击显示不同色调灰度的同一副图片。
**第二种方法:**
还是同样的思路,只不过不是在xml文件里,而是在java程序里更改点击显示不同图片,如下:
~~~
imageButton.setOnTouchListener(new OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
//更改为按下时的背景图片
v.setBackgroundResource(R.drawable.pressed);
}else if(event.getAction() == MotionEvent.ACTION_UP){
//改为抬起时的图片
v.setBackgroundResource(R.drawable.released);
}
return false;
}
});
~~~
**第三种方法:**
这也是我采用的方法。前两种方法在按键多的情况下用起来很不方便,当然不排除个别情况下就需要显示不同的图片。这里用颜色矩阵的方法,只需一张照片,就可以实现效果。且这个效果可以往任何一个ImageButton上绑定。代码如下:
~~~
/*为了使图片按钮按下和弹起状态不同,采用过滤颜色的方法.按下的时候让图片颜色变淡*/
public class MyOnTouchListener implements OnTouchListener{
public final float[] BT_SELECTED=new float[]
{ 2, 0, 0, 0, 2,
0, 2, 0, 0, 2,
0, 0, 2, 0, 2,
0, 0, 0, 1, 0 };
public final float[] BT_NOT_SELECTED=new float[]
{ 1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0 };
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
if(event.getAction() == MotionEvent.ACTION_DOWN){
v.getBackground().setColorFilter(new ColorMatrixColorFilter(BT_SELECTED));
v.setBackgroundDrawable(v.getBackground());
}
else if(event.getAction() == MotionEvent.ACTION_UP){
v.getBackground().setColorFilter(new ColorMatrixColorFilter(BT_NOT_SELECTED));
v.setBackgroundDrawable(v.getBackground());
}
return false;
}
}
~~~
可以用我http://blog.csdn.net/yanzi1225627/article/details/8577756这里的图片测试下,ImageButton按下后会有类似风车旋转的动画特效。
核心参考:
http://blog.sina.com.cn/s/blog_972ddc1b010113df.html
http://www.cnblogs.com/xiaowenji/archive/2011/02/04/1949165.html
http://blog.csdn.net/sytzz/article/details/5673662
2013新春奉送:Android摄像头开发完美demo—(循环聚焦,缩放大小,旋转picture,查询支持的picturesize, ImageButton按键效果)
最后更新于:2022-04-01 07:23:55
【**补充:我已在对此代码进行了全面的升级,升级后代码结构更加利于维护扩展,全面适配所有手机,参见[博文](http://blog.csdn.net/yanzi1225627/article/details/33028041)。此文中的资源也不要再下载了,请下载升级后的[代码](http://download.csdn.net/detail/yanzi1225627/7540873),如有问题请留言反馈,谢谢。---------------------------2014-6-23**】 ** **
**
**
【后注:】下载代码的注意,我的手机是4.3寸的屏,华为U9200.如果不能运行的请修改参数。看本篇的第四条。
除夕之夜,程序员还在编代码。[http://blog.csdn.net/yanzi1225627/article/details/7926994](http://blog.csdn.net/yanzi1225627/article/details/7926994)这是我几个月前写的代码,现在看来真是垃圾不堪阿。变量名字不规范,整个架构拉杂,几乎没有注释,程序不稳键,没有安全退出,导致摄像头下次不可用。
这个代码几乎涉及到了摄像头开发的所有方面,(除了PreviewCallback,这块东西我会结合android摄像头自动识别人脸/火灾来谈),且力求精简,是杂家的心血阿!相对之前改进之处有:
**1,精简。**只有一个ImageButton用来实现按下拍照。拍照后自动保存,进入预览界面。 不像原来的要三个按键:预览/拍照/保存。
**2,聚焦方面实现不间断循环聚焦**。 不像之前的,要按一下按键聚焦一次。
**3,ImageButton增加了按下的效果。**按之前示例如下:![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5455eaeed9.png)
,点击后背景变暗,有种风车旋转的感觉。
**4,增加了查询摄像头PictureSizes和PreviewSize的代码**,调试程序时应先查询出自己的参数然后配置。不同的手机参数不同。另外,预览surfaceView的高我设为800px,如果手机屏幕太小,这个参数要改。
5,改进了之前的按back返回按键退出程序后,再次进入程序camera没有释放,致使程序挂掉的问题。
6,改进了预览时手机横竖屏切换时,程序挂掉的毛病。但这里的布局还是采用默认的竖屏。
7,在实现循环聚焦的同时,保留了autoFocus()接口。可以测试出,在使用**FOCUS_MODE_CONTINUOUS_VIDEO**聚焦模式下,autoFocus不发挥作用。**如果不支持不间断聚焦,setFocusMode就改成**[**FOCUS_MODE_AUTO**](http://developer.android.com/reference/android/hardware/Camera.Parameters.html#FOCUS_MODE_AUTO)**!!!**
8,注释更加良好。
废话不说了请看源码:
**第一部分:Manifinest.xml**
~~~
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="yan.guoqi.rectphoto"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="15" />
<!-- 增加文件存储和访问摄像头的权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".RectPhoto"
android:label="@string/title_activity_rect_photo" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
~~~
**第二部分:布局文件**
~~~
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/BestWish"
tools:context=".RectPhoto" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<SurfaceView
android:id="@+id/previewSV"
android:layout_width="fill_parent"
android:layout_height="800px" />
</FrameLayout>
<ImageButton
android:id="@+id/photoImgBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/photo_img_btn"
android:layout_gravity="center" />
</LinearLayout>
~~~
**第三部分:RectPhoto.java主程序**
~~~
package yan.guoqi.rectphoto;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.ShutterCallback;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageButton;
public class RectPhoto extends Activity implements SurfaceHolder.Callback{
private static final String tag="yan";
private boolean isPreview = false;
private SurfaceView mPreviewSV = null; //预览SurfaceView
private SurfaceHolder mySurfaceHolder = null;
private ImageButton mPhotoImgBtn = null;
private Camera myCamera = null;
private Bitmap mBitmap = null;
private AutoFocusCallback myAutoFocusCallback = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//设置全屏无标题
requestWindowFeature(Window.FEATURE_NO_TITLE);
int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN;
Window myWindow = this.getWindow();
myWindow.setFlags(flag, flag);
setContentView(R.layout.activity_rect_photo);
//初始化SurfaceView
mPreviewSV = (SurfaceView)findViewById(R.id.previewSV);
mySurfaceHolder = mPreviewSV.getHolder();
mySurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);//translucent半透明 transparent透明
mySurfaceHolder.addCallback(this);
mySurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
//自动聚焦变量回调
myAutoFocusCallback = new AutoFocusCallback() {
public void onAutoFocus(boolean success, Camera camera) {
// TODO Auto-generated method stub
if(success)//success表示对焦成功
{
Log.i(tag, "myAutoFocusCallback: success...");
//myCamera.setOneShotPreviewCallback(null);
}
else
{
//未对焦成功
Log.i(tag, "myAutoFocusCallback: 失败了...");
}
}
};
mPhotoImgBtn = (ImageButton)findViewById(R.id.photoImgBtn);
//手动设置拍照ImageButton的大小为120×120,原图片大小是64×64
LayoutParams lp = mPhotoImgBtn.getLayoutParams();
lp.width = 120;
lp.height = 120;
mPhotoImgBtn.setLayoutParams(lp);
mPhotoImgBtn.setOnClickListener(new PhotoOnClickListener());
mPhotoImgBtn.setOnTouchListener(new MyOnTouchListener());
}
/*下面三个是SurfaceHolder.Callback创建的回调函数*/
public void surfaceChanged(SurfaceHolder holder, int format, int width,int height)
// 当SurfaceView/预览界面的格式和大小发生改变时,该方法被调用
{
// TODO Auto-generated method stub
Log.i(tag, "SurfaceHolder.Callback:surfaceChanged!");
initCamera();
}
public void surfaceCreated(SurfaceHolder holder)
// SurfaceView启动时/初次实例化,预览界面被创建时,该方法被调用。
{
// TODO Auto-generated method stub
myCamera = Camera.open();
try {
myCamera.setPreviewDisplay(mySurfaceHolder);
Log.i(tag, "SurfaceHolder.Callback: surfaceCreated!");
} catch (IOException e) {
// TODO Auto-generated catch block
if(null != myCamera){
myCamera.release();
myCamera = null;
}
e.printStackTrace();
}
}
public void surfaceDestroyed(SurfaceHolder holder)
//销毁时被调用
{
// TODO Auto-generated method stub
Log.i(tag, "SurfaceHolder.Callback:Surface Destroyed");
if(null != myCamera)
{
myCamera.setPreviewCallback(null); /*在启动PreviewCallback时这个必须在前不然退出出错。
这里实际上注释掉也没关系*/
myCamera.stopPreview();
isPreview = false;
myCamera.release();
myCamera = null;
}
}
//初始化相机
public void initCamera(){
if(isPreview){
myCamera.stopPreview();
}
if(null != myCamera){
Camera.Parameters myParam = myCamera.getParameters();
// //查询屏幕的宽和高
// WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
// Display display = wm.getDefaultDisplay();
// Log.i(tag, "屏幕宽度:"+display.getWidth()+" 屏幕高度:"+display.getHeight());
myParam.setPictureFormat(PixelFormat.JPEG);//设置拍照后存储的图片格式
// //查询camera支持的picturesize和previewsize
// List<Size> pictureSizes = myParam.getSupportedPictureSizes();
// List<Size> previewSizes = myParam.getSupportedPreviewSizes();
// for(int i=0; i<pictureSizes.size(); i++){
// Size size = pictureSizes.get(i);
// Log.i(tag, "initCamera:摄像头支持的pictureSizes: width = "+size.width+"height = "+size.height);
// }
// for(int i=0; i<previewSizes.size(); i++){
// Size size = previewSizes.get(i);
// Log.i(tag, "initCamera:摄像头支持的previewSizes: width = "+size.width+"height = "+size.height);
//
// }
//设置大小和方向等参数
myParam.setPictureSize(1280, 960);
myParam.setPreviewSize(960, 720);
//myParam.set("rotation", 90);
myCamera.setDisplayOrientation(90);
myParam.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
myCamera.setParameters(myParam);
myCamera.startPreview();
myCamera.autoFocus(myAutoFocusCallback);
isPreview = true;
}
}
/*为了实现拍照的快门声音及拍照保存照片需要下面三个回调变量*/
ShutterCallback myShutterCallback = new ShutterCallback()
//快门按下的回调,在这里我们可以设置类似播放“咔嚓”声之类的操作。默认的就是咔嚓。
{
public void onShutter() {
// TODO Auto-generated method stub
Log.i(tag, "myShutterCallback:onShutter...");
}
};
PictureCallback myRawCallback = new PictureCallback()
// 拍摄的未压缩原数据的回调,可以为null
{
public void onPictureTaken(byte[] data, Camera camera) {
// TODO Auto-generated method stub
Log.i(tag, "myRawCallback:onPictureTaken...");
}
};
PictureCallback myJpegCallback = new PictureCallback()
//对jpeg图像数据的回调,最重要的一个回调
{
public void onPictureTaken(byte[] data, Camera camera) {
// TODO Auto-generated method stub
Log.i(tag, "myJpegCallback:onPictureTaken...");
if(null != data){
mBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);//data是字节数据,将其解析成位图
myCamera.stopPreview();
isPreview = false;
}
//设置FOCUS_MODE_CONTINUOUS_VIDEO)之后,myParam.set("rotation", 90)失效。图片竟然不能旋转了,故这里要旋转下
Matrix matrix = new Matrix();
matrix.postRotate((float)90.0);
Bitmap rotaBitmap = Bitmap.createBitmap(mBitmap, 0, 0, mBitmap.getWidth(), mBitmap.getHeight(), matrix, false);
//保存图片到sdcard
if(null != rotaBitmap)
{
saveJpeg(rotaBitmap);
}
//再次进入预览
myCamera.startPreview();
isPreview = true;
}
};
//拍照按键的监听
public class PhotoOnClickListener implements OnClickListener{
public void onClick(View v) {
// TODO Auto-generated method stub
if(isPreview && myCamera!=null){
myCamera.takePicture(myShutterCallback, null, myJpegCallback);
}
}
}
/*给定一个Bitmap,进行保存*/
public void saveJpeg(Bitmap bm){
String savePath = "/mnt/sdcard/rectPhoto/";
File folder = new File(savePath);
if(!folder.exists()) //如果文件夹不存在则创建
{
folder.mkdir();
}
long dataTake = System.currentTimeMillis();
String jpegName = savePath + dataTake +".jpg";
Log.i(tag, "saveJpeg:jpegName--" + jpegName);
//File jpegFile = new File(jpegName);
try {
FileOutputStream fout = new FileOutputStream(jpegName);
BufferedOutputStream bos = new BufferedOutputStream(fout);
// //如果需要改变大小(默认的是宽960×高1280),如改成宽600×高800
// Bitmap newBM = bm.createScaledBitmap(bm, 600, 800, false);
bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);
bos.flush();
bos.close();
Log.i(tag, "saveJpeg:存储完毕!");
} catch (IOException e) {
// TODO Auto-generated catch block
Log.i(tag, "saveJpeg:存储失败!");
e.printStackTrace();
}
}
/*为了使图片按钮按下和弹起状态不同,采用过滤颜色的方法.按下的时候让图片颜色变淡*/
public class MyOnTouchListener implements OnTouchListener{
public final float[] BT_SELECTED=new float[]
{ 2, 0, 0, 0, 2,
0, 2, 0, 0, 2,
0, 0, 2, 0, 2,
0, 0, 0, 1, 0 };
public final float[] BT_NOT_SELECTED=new float[]
{ 1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0 };
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
if(event.getAction() == MotionEvent.ACTION_DOWN){
v.getBackground().setColorFilter(new ColorMatrixColorFilter(BT_SELECTED));
v.setBackgroundDrawable(v.getBackground());
}
else if(event.getAction() == MotionEvent.ACTION_UP){
v.getBackground().setColorFilter(new ColorMatrixColorFilter(BT_NOT_SELECTED));
v.setBackgroundDrawable(v.getBackground());
}
return false;
}
}
@Override
public void onBackPressed()
//无意中按返回键时要释放内存
{
// TODO Auto-generated method stub
super.onBackPressed();
RectPhoto.this.finish();
}
}
~~~
源码下载链接: [http://download.csdn.net/detail/yanzi1225627/5060323](http://download.csdn.net/detail/yanzi1225627/5060323)
欢迎android爱好者加群248217350,备注:yanzi
**注:代码不能正常运行的请看上面第4条,修改相关参数。
**----------------------------------------------------------------------------------------本文系原创,转载请注明作者:yanzi1225627
Android开发:安装NDK,移植OpenCV2.3.1,JNI调用OpenCV全过程
最后更新于:2022-04-01 07:23:53
开发环境:Fedora14 , 操作中以root权限操作,这年头Android移植上OpenCV就强大了,可以做很多复杂的视频分析、图像处理工作了!如火灾、人脸、视频行为的分析等。欢迎Android OpenCV爱好者,加入OpenCV4Android联盟群:66320324。备注:yanzi
**一:NDK的安装**
首先是安装NDK,安装之前需要安装CDT,具体的参照[http://blog.csdn.net/yanzi1225627/article/details/7736364](http://blog.csdn.net/yanzi1225627/article/details/7736364)这里来完成。这里再详细记录下NDK的安装过程。google下载NDK,也可以点击这里下载:http://download.csdn.net/detail/yanzi1225627/5015893,下载后输入tar -jxvf android-ndk-r8-linux-x86.tar.bz2 -C /usr/local/android/将其解压到/usr/local/android/ 目录。『注,我的android相关文件都安装在这里。这个目录不是死的。』然后gedit /etc/profile,在里面添加:export PATH=$PATH:/usr/local/android/android-ndk-r8,从安装JDK到eclipse, android, NDK,在/etc/profile文件里,添加的命令一共有如下三条:
export JAVA_HOME=/usr/local/android/jdk1.7.0_04
export PATH=$PATH:$JAVA_HOME/bin
export PATH=$PATH:/usr/local/android/android-ndk-r8
只要这三条就行了。然后source /etc/profile 使刚才的设置生效。
在终端里输入:ndk-build,可以测试出ndk安装成功了么有。
注:有的时候也可以通过在线安装方式,在eclipse里点help-install new software,网址输入这个:ADT - https://dl-ssl.google.com/android/eclipse/,也会看到安装NDK Plugins, 如果经过上面的步骤,NDK不能用就把这个也装上。 但如果只装这个,好像不中,找不到ndk安装的目录,也无法配置路径。
**二:OpenCV的移植**
这里的移植不是像在qt下那么麻烦,这是因为OpenCV退出来直接支持Android的版本,所以不需要自己编译。直接下下来,解压缩就可以了。我用的OpenCV2.3.1,需要的可以直接到csdn资源里下载,链接:[http://download.csdn.net/detail/yanzi1225627/5013701](http://download.csdn.net/detail/yanzi1225627/5013701),也可以自己到sourceforge上下载,链接:http://sourceforge.net/projects/opencvlibrary/files/opencv-android/,从这里可以看出从OpenCV2.3开始就有编译好的android版本。最新的是2.4.3版本,在2012年12月24发布的。牛逼阿!
接下来就是配置。事实上有两种方法在Android里调用OpenCV,一种是使用OpenCV java Api,一种是通过JNI的方式。这里是针对后者。将OpenCV-2.3.1-android-bin.tar.bz2解压缩,然后将里面的OpenCV-2.3.1拷贝到Eclipse工作空间的平级目录。图示:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21abeac19.png)
为此,我们现先建一个工作空间。新建文件夹/home/yan/TestOpenCV,点eclipse里的File---Switch workspace---other,选中这个目录。切换到这个工作空间后,点Window---Preference--android,选中自己的android-sdk的安装目录,我的是:/usr/local/android/android-sdk-linux。然后就可以在这个工作空间里正常android开发了,如果不设置这个,新建的工程全是红叉叉。
新建一个项目HaveImgFun,包名是package com.testopencv.haveimgfun; 然后将刚才解压缩出来的**OpenCV-2.3.1-android-bin\samples"下的includeOpenCV.mk文件拷贝到和项目HaveImgFun同一级目录**中。图示:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21ac095a3.png)
在 eclipse里选中那个项目,新建一个文件夹jni,然后新建文件:Android.mk,里面的内容是:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
include ../includeOpenCV.mk
ifeq ("$(wildcard $(OPENCV_MK_PATH))","")
#try to load OpenCV.mk from default install location
include $(TOOLCHAIN_PREBUILT_ROOT)/user/share/OpenCV/OpenCV.mk
else
include $(OPENCV_MK_PATH)
endif
LOCAL_MODULE := ImgFun
LOCAL_SRC_FILES := ImgFun.cpp
include $(BUILD_SHARED_LIBRARY)
再新建一个Application.mk, 内容输入:
APP_STL:=gnustl_static
APP_CPPFLAGS:=-frtti -fexceptions
APP_ABI:=armeabi armeabi-v7a
然后新建一个cpp文件,ImgFun.cpp, 这个测试程序是将一个图片的上半部分弄黑,然后复原。具体的大家看源码。功能很简单。
内容是:
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <opencv2/opencv.hpp>
using namespace cv;
extern "C" {
JNIEXPORT jintArray JNICALL Java_com_example_haveimgfun2_LibImgFun_ImgFun(
JNIEnv* env, jobject obj, jintArray buf, int w, int h);
JNIEXPORT jintArray JNICALL Java_com_testopencv_haveimgfun_LibImgFun_ImgFun(
JNIEnv* env, jobject obj, jintArray buf, int w, int h){
jint *cbuf;
cbuf = env->GetIntArrayElements(buf, false);
if(cbuf == NULL)
{
return 0;
}
Mat myimg(h, w, CV_8UC4, (unsigned char*)cbuf);
for(int j=0; j<myimg.rows/2; j++)
{
myimg.row(j).setTo(Scalar(0, 0, 0, 0));
}
int size=w*h;
jintArray result = env->NewIntArray(size);
env->SetIntArrayRegion(result, 0, size, cbuf);
env->ReleaseIntArrayElements(buf, cbuf, 0);
return result;
}
}
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21ac16d50.png)
然后在终端里切换到HaveImgFun目录,也就是在目录/home/yan/TestOpenCV/HaveImgFun下, 终端输入**ndk-build**, 会生成相应的库。
具体的大家下载源程序把!下载连接:[http://download.csdn.net/detail/yanzi1225627/5016365](http://download.csdn.net/detail/yanzi1225627/5016365)
【注,下载后将文件解压。然后eclipse里,切换到TestOpenCV空间,点File--import,就可以了。首先测下最后一步,也就是输入ndk-build, 会不会生成相应的库!】
参考:[http://www.cnblogs.com/ldr213/archive/2012/02/20/2359262.html](http://www.cnblogs.com/ldr213/archive/2012/02/20/2359262.html)
Android开发 摄像头SurfaceView预览 背景带矩形框 实现(原理:双surfaceview,顶层画矩形框,底层预览视频)
最后更新于:2022-04-01 07:23:51
为了能在摄像头预览的时候,背景有个矩形框、或一些坐标、横线来标示关键位置,真让杂家费劲心思了。苦苦研究了两天,毫无进展。baidu了若干资料,大都是提出这个问题,但怎么解决的没有说,都不了了之。后来转而google,又研究了两天,终于完美解决啦!
1,网上介绍的摄像头预览,一般是用一个surfaceview,为了能让其正常预览视频,设置属性
mySurfaceView.setZOrderOnTop(true);
mySurfaceHolder = mySurfaceView.getHolder();
mySurfaceHolder.setFormat(PixelFormat.TRANSPARENT)
我先前也是在这种思路下,看能否加画个矩形框。这篇[http://blog.sina.com.cn/s/blog_4e6922ab01010gfz.html](http://blog.sina.com.cn/s/blog_4e6922ab01010gfz.html)文章提到一种方法,是用一个surfaceview,然后自己定义一个继承View的画矩形框的类DrawCaptureRect,经我实践,发现还是不中。用这种思路,在摄像头未预览的时候可以看到红色矩形框,但一点击预览,矩形框就被覆盖了。
2,还有一种思路是,在预览surfaceview的callback里面,取到canvas画布,然后画。这种思路也行不通,程序直接挂掉!
3,后来我想到了framelayout,用帧布局,融合第一种方法的思路,结果还是未能解决预览的时候,视频遮挡矩形框的问题。
4,还有人提出,给surfaceview或其所在的布局文件里加一个带矩形框的图片的方法,更是行不通!说这话的人真是站着不腰疼,别说背景设成带图片的矩形框,就是背景设成有颜色的,预览的surfaceview就不显示了,被遮挡了。
**原来,当SurfaceHolder对象的类型设置为SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS时就只能拍照不能绘制了**,这就是为什么第二种思路程序会直接挂掉的原因。为了能够预览视频的同时绘制矩形框等信息,需要用两个同样大小的SurfaceView放在一个FrameLayout里,顶层的SurfaceView设成setZOrderOnTop(true); setFormat(PixelFormat.TRANSPARENT) ;预览的Surfaceview这两个属性就不用设置了。
首先在包yan.guoqi.testphoto下新建一个类SVDraw,如图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21abae3df.jpg)
源码如下:
~~~
package yan.guoqi.testphoto;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
/*定义一个画矩形框的类*/
public class SVDraw extends SurfaceView implements SurfaceHolder.Callback{
protected SurfaceHolder sh;
private int mWidth;
private int mHeight;
public SVDraw(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
sh = getHolder();
sh.addCallback(this);
sh.setFormat(PixelFormat.TRANSPARENT);
setZOrderOnTop(true);
}
public void surfaceChanged(SurfaceHolder arg0, int arg1, int w, int h) {
// TODO Auto-generated method stub
mWidth = w;
mHeight = h;
}
public void surfaceCreated(SurfaceHolder arg0) {
// TODO Auto-generated method stub
}
public void surfaceDestroyed(SurfaceHolder arg0) {
// TODO Auto-generated method stub
}
void clearDraw()
{
Canvas canvas = sh.lockCanvas();
canvas.drawColor(Color.BLUE);
sh.unlockCanvasAndPost(canvas);
}
public void drawLine()
{
Canvas canvas = sh.lockCanvas();
canvas.drawColor(Color.TRANSPARENT);
Paint p = new Paint();
p.setAntiAlias(true);
p.setColor(Color.RED);
p.setStyle(Style.STROKE);
//canvas.drawPoint(100.0f, 100.0f, p);
canvas.drawLine(0,110, 500, 110, p);
canvas.drawCircle(110, 110, 10.0f, p);
sh.unlockCanvasAndPost(canvas);
}
}
~~~
类里面的方法drawLine()就是画预览视频时背景矩形的,由于我需画一条线,上面程序就是画一条线和一个圆的。需要画什么图形,就在这里改!
注意在建这个类时构造函数里一定要有
1. public SVDraw(Context context, AttributeSet attrs) {
1. super(context, attrs);
AttributeSet attrs这个参数,否则是不能在xml文件里正常解析的。先前提到的[http://blog.sina.com.cn/s/blog_4e6922ab01010gfz.html](http://blog.sina.com.cn/s/blog_4e6922ab01010gfz.html)这位就没有引用这个参数,所以后来用了
addContentView(mDraw, new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT)) 将这个对象加到布局中去!
**上面的类建好之后,在布局文件里引用他,如下:**
~~~
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<FrameLayout
android:id="@+id/myFramelayout"
android:layout_width="fill_parent"
android:layout_height="800px"
android:orientation="vertical" >
<SurfaceView
android:id="@+id/mySurfaceView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:visibility="visible" />
<yan.guoqi.testphoto.SVDraw
android:id="@+id/mDraw"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</FrameLayout>
<LinearLayout
android:id="@+id/LinearLayout01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal"
android:paddingTop="10dip" >
<Button
android:id="@+id/btnPreview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="预览"
android:textSize="28sp" />
<Button
android:id="@+id/btnPhoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="拍照"
android:textSize="28sp" />
<Button
android:id="@+id/btnSave"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="保存"
android:textSize="28sp" />
</LinearLayout>
</LinearLayout>
~~~
注意上面两个surfaceview是同样大小的,mySurfaceView是预览视频的,mDraw是用来画矩形的。
接下来就是在主Activity里引用这个mDraw。首先定义变量private SVDraw mSVDraw = null; 然后mSVDraw = (yan.guoqi.testphoto.SVDraw)findViewByIf(R.id.mDraw)。之后mSVDraw就可以用了,至于什么时候来画这个矩形,有人是在按下拍照按钮后,我选择的是在按下预览按钮后,
~~~
case R.id.btnPreview:
Toast.makeText(TestPhotoActivity.this,
"您按了预览按钮",
Toast.LENGTH_SHORT).show();
initCamera();
mDraw.setVisibility(View.VISIBLE);
mDraw.drawLine();
break;
~~~
让其可见,然后调用drawLine()方法就可以了!你可以随时控制让其可见或者隐藏!
将上面这些代码融合我[http://blog.csdn.net/yanzi1225627/article/details/7926994](http://blog.csdn.net/yanzi1225627/article/details/7926994)文章里[http://download.csdn.net/detail/yanzi1225627/4538626](http://download.csdn.net/detail/yanzi1225627/4538626)这里的源码就可以了!即可以预览,自动聚焦,缩放标准大小,背景有矩形框!
来张图,有图有真相![大笑](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21abc5518.gif)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21abd1d49.jpg)
反思:其实用第一种[http://blog.sina.com.cn/s/blog_4e6922ab01010gfz.html](http://blog.sina.com.cn/s/blog_4e6922ab01010gfz.html)这里的思路和本篇的思路合并起来,应该也是可行的,只不过一个是继承的view一个是surfaceview,将AttributeSet attrs加上。只要处理好谁是顶层的view谁设成透明,预览视频的surfaceview设成底层,在且要在xml属性文件里设成visible就可以了。有兴趣的自己试验吧。感谢大牛[http://blog.csdn.net/xinzheng_wang/article/details/7712285](http://blog.csdn.net/xinzheng_wang/article/details/7712285)对后生的指点!
-------------------作者yanzi1225627,转载请注明
Linux下使用QT调用opencv读取摄像头视频 调试心得
最后更新于:2022-04-01 07:23:49
因为做项目要用这,也是白手起家。今天抽空把心得写下,希望大家多顶哈!最初使用v4l2,但发现工作量太大了,而且自己写的代码不如opencv这么专业!
参照http://blog.csdn.net/hsl17/article/details/6925876,及http://www.linuxidc.com/Linux/2011-11/47347.htm等几篇文章,最终将其调试成功。
第一篇文章http://blog.csdn.net/hsl17/article/details/6925876已经说的很详细了,但有个地方没有说清。我将其补全:
MyWidget即是显示视频的widget,其头文件如下:
![](image/d41d8cd98f00b204e9800998ecf8427e.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21ab83431.jpg)
对应的构造函数为:
1. capture = cvCaptureFromCAM(0);
1. cvSetCaptureProperty(capture,CV_CAP_PROP_FRAME_WIDTH,320);
1. cvSetCaptureProperty(capture,CV_CAP_PROP_FRAME_HEIGHT,240);
1. if(capture)
1. {
1. QMessageBox::information(this,"Information","successful!");
1. if (capture)
1. {
1. frame = cvQueryFrame(capture);
1. if (frame)
1. this->resize(frame->width,frame->height);
1. qImg = new QImage(QSize(frame->width,frame->height),QImage::Format_RGB888);
1. iplImg = cvCreateImageHeader(cvSize(frame->width,frame->height),8,3);
1. iplImg->imageData = (char*)qImg->bits();
1. timer = new QTimer(this);
1. timer->setInterval(30);
1. connect(timer,SIGNAL(timeout()),this,SLOT(nextFrame()));
1. timer->start();
1. writer = cvCreateVideoWriter("out.avi",CV_FOURCC('D', 'I', 'V', 'X'),10,
1. cvSize(frame->width,frame->height),1);
1. isCamera = true;
1. }
1. }
1. else
1. {
1. QMessageBox::information(this,"Information","Sorry,fail!");
1. isCamera = false;
1. }
1. 这里面有个问题,第一,程序无法运行,经检查是
1. cvSetCaptureProperty(capture,CV_CAP_PROP_FRAME_WIDTH,320);
1. cvSetCaptureProperty(capture,CV_CAP_PROP_FRAME_HEIGHT,240);
1.
1. 这两句话的问题。查了一圈资料,貌似不支持。将其屏蔽!注释掉,程序就能运行了!
1. 第二,变量isCamera应该申明成为全局变量。按照原博客里的申明方法根本不可运行。将其申明为:static bool isCamera = false;这句话放在头文件的外面!!!
1. 其他部分参照下面程序即可!
1. void MyWidget::paintEvent(QPaintEvent *e)
1. {
1. QPainter painter(this);
1. if(isCamera == true)
1. {
1. painter.drawImage(QPoint(0,0),*qImg);
1. }
1. else
1. {
1.
1. }
1. }
1. void MyWidget::nextFrame()
1. {
1. frame = cvQueryFrame(capture);
1.
1. if (frame)
1. {
1. if (frame->origin == IPL_ORIGIN_TL)
1. {
1. cvCopy(frame,iplImg,0);
1. }
1. else
1. {
1. cvFlip(frame,iplImg,0);
1. }
1. cvCvtColor(iplImg,iplImg,CV_BGR2RGB);
1. cvWriteFrame(writer,frame);
1. this->update();
1. }
1. }
1. 疑问:
1. 第一,有时候运行程序会卡住,直接出不来图像。在MyWidget的析构函数中增加cvReleaseImage(&frame)好了很多。另外定时器的间隔时间调40ms、50ms会好点。我的电脑内存是4G,当内存占用1300多M时,运行程序卡的几率很大。重启后卡的几率就变小了。 不知道为什么??
1. 第二,每次运行程序时,在显示图片之前会有警告。mmap:无效的参数
1. munmap:无效的参数
1. munmap;无效的参数 。。。。截图:
1. ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5455e73c9f.jpg)
我查了很多资料貌似是内存申请的问题。我在其他网站下载了另外一个读取摄像头视频的程序,也可以运行。程序构建和上述有点不同,但也是会出现这些警告。这些警告过后就能照常运行了。 我猜测这是第一帧图片没有正确申请内存的原因。解决办法正在思考zhong. 也希望高人过来指点!!!
前言
最后更新于:2022-04-01 07:23:46
> 原文出处:[Android(OpenCV)开发](http://blog.csdn.net/column/details/android-opencv.html)
作者:[晏国淇](http://blog.csdn.net/yanzi1225627)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
# Android(OpenCV)开发
> Android开发中遇到的问题及解决,尤其是Camera模块及移植OpenCV,Android结合OpenCV的生物特征识别,如掌纹识别 人脸识别 火灾检测等。