65.9K
CodeProject 正在变化。 阅读更多。
Home

Android 触摸手势捕获界面

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (6投票s)

2014年8月23日

CPOL

5分钟阅读

viewsIcon

24996

downloadIcon

88

在本文中,我们将探讨针对 Android 设备的单笔画手势识别。

引言

在本文中,我们将研究一个用于捕捉触摸手势的 Android 应用程序。此模块是通用触摸式手势识别库的第一部分。

背景

手势是预先录制的触摸屏运动序列。手势识别是模式识别、图像分析和计算机视觉领域一个活跃的研究领域。

我们将有几种应用程序可以运行的模式。用户可以选择的选项之一是捕捉和存储候选手势。

目标是构建一个通用的 C/C++ 库,该库可以以用户定义的格式存储手势。

手势注册 Android 接口

捕捉和存储候选手势类别信息的这个过程称为手势注册。

在本文中,我们将使用 GestureOverlay 方法。手势覆盖区就像一个简单的绘图板,用户可以在上面绘制手势。用户可以修改几个视觉属性,例如用于绘制手势的笔触的颜色和宽度,并注册各种监听器来跟踪用户的操作。

要捕捉和处理手势,第一步是将 GestureOverlayView 添加到 store_gesture.xml XML 布局文件中。

<?xml version="1.0" encoding="utf-8"?>

......
......

<android.gesture.GestureOverlayView 
android:id="@+id/gestures"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:eventsInterceptionEnabled="true"
android:gestureStrokeType="multiple"
android:orientation="vertical" >

    
 <LinearLayout
        style="@android:style/ButtonBar"

        android:layout_width="match_parent"
        android:layout_height="wrap_content"

        android:orientation="horizontal">

        <Button
            android:id="@+id/done"
                
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"

            android:enabled="false"

            android:onClick="addGesture"
            android:text="@string/button_done" />
    
        <Button
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            
            android:onClick="cancelGesture"
            android:text="@string/button_discard" />
    
    </LinearLayout>
 
</android.gesture.GestureOverlayView>

指定了一些手势覆盖区的属性,例如手势笔触类型是单笔画,表示单笔画手势。

现在,在主活动文件中,我们只需要将内容视图设置为布局文件。在当前的应用程序中,布局文件的名称是“activity_open_vision_gesture.xml”。

由于我们还需要捕捉或处理手势一旦被执行,我们向覆盖区添加一个手势监听器。最常用的监听器是 GestureOverlayView.OnGesturePerformedListener,它会在用户完成绘制手势时触发。我们使用一个名为 GesturesProcessor 的类,该类实现了 GestureOverlayListener

一旦用户绘制完手势,控制流就会进入 onGestureEnded 方法。在这里,我们可以复制手势并执行一系列活动,例如存储、预测等。

下面是 UI 界面的一张图片

private class GesturesProcessor implements GestureOverlayView.OnGestureListener {
        public void onGestureStarted(GestureOverlayView overlay, MotionEvent event) {
            mDoneButton.setEnabled(false);
            mGesture = null;
        }

        public void onGesture(GestureOverlayView overlay, MotionEvent event) {
        }

        //callback function entered when the gesture registration is completed
        public void onGestureEnded(GestureOverlayView overlay, MotionEvent event) {
           //copy the gesture to local variable
            mGesture = overlay.getGesture();
          //ignore the gesture if length is below a threshold
            if (mGesture.getLength() < LENGTH_THRESHOLD) {
                overlay.clear(false);
            }
          //enable the store button
            mDoneButton.setEnabled(true);
        }

        public void onGestureCancelled(GestureOverlayView overlay, MotionEvent event) {
        }
    }

点击存储按钮后,程序进入“onStore”回调函数。

    public void addGesture(View v) {
        Log.e("CreateGestureActivity","Adding Gestures");
        //function which extracts information from the Android Gesture Objects 
        //like locations and then make native library function calls to store the gesture
        extractGestureInfo();
    }

我们在 GestureLibraryInterface 类中定义了所有的 JNI 接口函数。我们定义了 2 个 JNI 接口调用到原生的 C/C++ 手势库。

public class GestureLibraryInterface {
    static{Loader.load();}
    //makes native calls to GestureLibrary to store gesture information in local filesystem
    public native static void addGesture(ArrayList%lt;Float> location,ArrayList<Long> time,String name);
    //make native calls to GestureLibrary to set the gesture directory
    public native static void setDirectory(String name);
}

第一步是通过调用“setDirectory”来设置存储手势的目录。这在“onCreate”函数中初始化 AndroidActivity 时完成。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        setContentView(R.layout.store_gesture);

        mDoneButton = findViewById(R.id.done);
        eText = (EditText) findViewById(R.id.gesture_name);
        GestureOverlayView overlay = (GestureOverlayView) findViewById(R.id.gestures);
        overlay.addOnGestureListener(new GesturesProcessor());
        GestureLibraryInterface.setDirectory(DIR);
    }

extractGestureInfo 读取手势笔触并将位置存储在 ArrayList 中,该列表通过 JNI 接口传递到原生的 C/C++。

.....
private static final String DIR=Environment.getExternalStorageDirectory().getPath()+"/AndroidGesture/v1";
....
      @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //load the store activity GUI layoyt files
        setContentView(R.layout.store_gesture);
        //gets the store button object
        mDoneButton = findViewById(R.id.done);
        //get the EditText object
        eText = (EditText) findViewById(R.id.gesture_name);
        //configures the gestureOverLay Listener
        GestureOverlayView overlay = (GestureOverlayView) findViewById(R.id.gestures);
        overlay.addOnGestureListener(new GesturesProcessor());
        //gets the gesture directory  
        GestureLibraryInterface.setDirectory(DIR);
    }

与 Java 类相关的 JNI C/C++ 代码定义在 GestureLibraryInterface.cppGestureLibraryInterface.hpp 文件中。

//function calls the GetureRegognizer methods to add gesture to class path
JNIEXPORT void JNICALL Java_com_openvision_androidgesture_GestureLibraryInterface_addGesture(JNIEnv *, jobject, jobject, jobject, jstring);

//function calls the GestureRecognizer methods to set the main gesture directory path
JNIEXPORT void JNICALL Java_com_openvision_androidgesture_GestureLibraryInterface_setDirectory(JNIEnv *, jobject, jstring);

//Utility functions to convert from jobject datatype to float and Long
float getFloat(JNIEnv *env,jobject value);
long getLong(JNIEnv *env,jobject value);

UniStrokeGesture 库包含以下文件

  • UniStrokeGestureLibrary
  • UniStrokeGesture
  • GesturePoint

UniStrokeGestureLibrary 类封装了单笔画手势的所有属性。它包含用于存储、检索和预测手势等方法。

addGesture”JNI 方法调用类中实现的保存例程来存储手势。

UniStrokeGestureLibrary 由一系列 UniStrokeGesture 类型的对象组成。

UniStrokeGesture 类的对象封装了单个手势类的所有属性。UniStrokeGesture 类包含存储多个样本手势实例的设施,因为 **单笔画** 手势可以由多个候选实例表示。

UniStrokeGesture 包含一系列 GesturePoint 类型的对象。每个 GesturePoint 代表 UniStrokeGesture 的一个元素,并以其在 2D 网格中的位置为特征。

/**
 *  function that stores the Gesture to a specified directory
 */
void UniStrokeGestureRecognizer::save(string dir,vector<gesturepoint> points)
{

    char abspath1[1000],abspath2[1000];
    sprintf(abspath1,"%s/%s",_path.c_str(),dir.c_str());
   
    int count=0;

    //check if directory exists else create it
    int ret=ImgUtils::createDir((const char *)abspath1);

    count=ImgUtils::getFileCount(abspath1);

     sprintf(abspath2,"%s/%d.csv",abspath1,count);
     //writing contents to file in csv format
     ofstream file(abspath2,std::ofstream::out);
     for(int i=0;i<points.size();i++)
     {
         GesturePoint p=points[i];
         file << p.position.x <<",";
         file << p.position.y<< ","
     }

     file.close();
     //creating a bitmap while storing the CSV file
     generateBitmap(abspath2);     
}

考虑一个以 CSV 格式存储的手势示例

1.CSV

generateBitMap 函数从输入 CSV 文件加载手势点,并生成适合显示的位图图像。

void UniStrokeGestureRecognizer::generateBitmap(string file)
{
   string basedir=ImgUtils::getBaseDir(file);
   string name=ImgUtils::getBaseName(file);
   string line;
   //ifstream classFile(file.c_str());

   
   float x,y;
   cv::Mat image=cv::Mat(640,480,CV_8UC3);
   image.setTo(cv::Scalar::all(0));
   Point x1,x2,x3;
   int first=-1;
   int delta=20;
   vector<gesturepoint> points;
   //loading the gesture from CSV file
   points=loadTemplateFile(file.c_str(),"AA");

   //getting the bounding box
   Rect R=boundingBox(points);
   
   //drawing the gesture
   int i=0;
   cv::circle(image,cv::Point((int)points[i].position.x,(int)points[i].position.y),3,cv::Scalar(255,255,0),-1,CV_AA);
   for(i=1;i<points.size();i++) x2="cv::Point((int)points[i-1].position.x,
   (int)points[i-1].position.y);" x1="cv::Point((int)points[i].position.x,
   (int)points[i].position.y);" r="Rect(max(0,R.x-delta),max(0,R.y-delta),
   R.width+2*delta,R.height+2*delta);">image.cols)
       R.width=image.cols-R.x-1;
   if(R.y+R.height>image.rows)
       R.height=image.rows-R.y-1;

   //extract the ROI
   Mat roi=image(R);
   Mat dst;
   cv::resize(roi,dst,Size(640,480));
   string bmpname=basedir+"/"+name+".bmp";
   //save the bitmap
   cv::imwrite(bmpname,dst);
}

显示手势列表

应用程序的下一部分处理在 Android UI 上显示上面部分创建的手势。

启动应用程序后,将加载模板目录中的所有 bmp 文件。

加载手势位图的活动以异步方式在后台进行。

在 Android 中,ListView 用于显示手势位图和相关文本。

列表中每个项目的布局定义在 gesture_item.xml 文件中。

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"

    android:gravity="center_vertical"
    android:minHeight="?android:attr/listPreferredItemHeight"

    android:drawablePadding="12dip"
    android:paddingLeft="6dip"
    android:paddingRight="6dip"

    android:ellipsize="marquee"
    android:singleLine="true"
    android:textAppearance="?android:attr/textAppearanceLarge" />

ListView 的布局在主布局文件 activity_open_vision.xml 中定义。

displayGestures 函数定义在 OpenVisionGesture.java 中。

在主类文件中定义了一个名为 GesturesLoadTask 的“AsyncTask”对象。这些类的方法从 displayGestures 函数调用,该函数在后台加载手势列表。

ArrayTask 类的对象需要定义这些方法

  • doInBackground - 在后台执行的主函数
  • onPreExecute - 在后台任务执行之前调用的函数
  • onPostExecute - 在后台任务执行之后调用的函数
  • onProgressUpdate - 函数可用于在后台任务执行期间更新 UI 内容

在后台任务中,代码解析手势模板目录并读取所有位图文件。

ArrayAdapter 接受一个数组并将项目转换为 View 对象以加载到 ListView 容器中。我们定义一个适配器,它维护一个 NamedGesture 对象,该对象包含手势名称和标识符。“getView”函数在 ArrayAdapter 类中负责将 Java 对象转换为 View。

我们维护一个由 ID 标识的位图列表以及由 SameID 表示的手势名称列表。

每当读取一个位图时,我们都会更新列表和 GUI,以便通过调用“publishProgress”函数进行显示,该函数会导致在主 UI 线程中调用 onProgressUpdate 函数。

使用这种方法,我们可以看到位图随着时间的推移而填充

        @Override
        protected Integer doInBackground(Void... params) {
            if (isCancelled()) return STATUS_CANCELLED;
            if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
                return STATUS_NO_STORAGE;
            }            

            Long id=new Long(0);
            File list = new File(CreateGestureActivity.DIR);
            
            //get list of template classes
            File[] files = list.listFiles(new DirFilter());
            
            for(int i=0;i<files.length;i++)
            {
                //get list of image files in the template folder
                File[] list1=files[i].listFiles(new ImageFileFilter());
                for(int k=0;k<list1.length;k++)
                { 
                            //load the image files
                      BitmapFactory.Options options = new BitmapFactory.Options();
                      options.inPreferredConfig = Bitmap.Config.ARGB_8888;
                     Bitmap bitmap = BitmapFactory.decodeFile(list1[k].getPath(), options);                
                          Bitmap ThumbImage = ThumbnailUtils.extractThumbnail(bitmap, mThumbnailSize, mThumbnailSize);
               
                         final NamedGesture namedGesture = new NamedGesture();

                             namedGesture.id=id;
                             namedGesture.name = files[i].getName()+"_"+list1[k].getName();
                             //add bitmap to hashtable
                             mAdapter.addBitmap((Long)id, ThumbImage);
                             id=id+1;
                             //update the GUI
                             publishProgress(namedGesture);
                             bitmap.recycle();
                }                
            }

            return STATUS_SUCCESS;
        }   

一旦调用了适配器中的 add 函数,UI ListView 就会通过在“getView”函数中显示手势名称和关联的位图来更新。

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                //view associated with individual gesture item
                convertView = mInflater.inflate(R.layout.gestures_item, parent, false);
            }
            //get the gesture at specified position in the listView
            final NamedGesture gesture = getItem(position);
            final TextView label = (TextView) convertView;

            //set the gesture names
            label.setTag(gesture);
            label.setText(gesture.name);
            //get the bitmap from hashtable identified by id and display bitmap to left of text
            label.setCompoundDrawablesWithIntrinsicBounds(mThumbnails.get(gesture.id),null, null, null);

            return convertView;
        }

代码

ImgApp 目录中的文件来自 OpenVision 存储库,可在 github 存储库 www.github.com/pi19404/OpenVision. 中找到。

完整的 Android 项目可以在 OpenVision 存储库的 samples/Android/AndroidGestureCapture 目录中找到。 这是 Android 项目源包,可以直接导入到 Eclipse 中运行。该应用程序已在 Android 版本 4.1.2 的移动设备上进行了测试。在开发应用程序时,并未测试或考虑与其他 Android OS 版本的兼容性。

您需要在系统上安装 OpenCV。本应用程序是在 Ubuntu 12.04 OS 上开发的。Android.mk 中的路径是基于此指定的。对于 Windows 或其他 OS,或者如果 OpenCV 路径不同,请相应地修改 make 文件。

可以在文章顶部的链接处下载 apk 文件。

© . All rights reserved.