在Android上用手指抹画处理图像

        想实现的效果是在Android设备上,用手指在图片上抹画以选定一个不规则区域,而且在抹画的同时实时处理已选择的区域。

        关于如何在Windows中配置Android+OpenCV的环境,可以参考《Android上使用OpenCV处理图像》

        我的开发环境:

               Windows 7 x64

               Eclipse 4.4.0 32 Bit

               Android SDK 22.3

               OpenCV 2.4.8 Android SDK


界面与原理

        主界面有一个显示区域imageview,一个下拉列表spinner,一个滑动条seekbar,四个按钮btnBack、btnFill、btnSave、 btnSaveImage。

shinee-main

        imageview用来显示图片;spinner用来选择图像处理的方法;seekbar用于调节图像处理的参数;按钮btnBack用于返回图片选择界面(该界面的的代码见下文);按钮btnFill用来将当前的操作应用到整幅图片,而不只处理手指选择的区域;按钮btnSave用于暂时保存现在的效果,以便更换其他处理方式继续处理;按钮btnSaveImage则把处理的结果输出为JPEG文件。

shinee-spinner

        在spinner中选择一种方法后,便通过JNI调用C函数以使用OpenCV对整幅图像进行相应处理,并把结果保存在imgProc中。当调节seekbar的滑块时,实时调用同一C函数,只不过传入的参数是滑块位置改变后所代表的参数。在这个过程中imageview始终显示的是原始的图片。

        为了把改变应用到图片上,为Activity添加onTouchEvent,获得手指触摸的位置,同时获取imageview当前所显示的图像数据,与imgProc所保存的图像数据一同传递到一个名为combine的自定义C函数,该函数的作用是以触摸的位置为中心,以给定值(hdpi屏幕上为56)为半径画一个圆,圆内区域取imgProc的值,在圆外则取imageview的值,并把获得的图像返回。然后把这个返回的图像绘制到imageview中。

        这样,当手指触摸并移动时,程序会获得上一时刻的imageview,在当前时刻手指所在的圆形内进行处理,再用获得的结果来更新imageview,就实现了实时处理手指抹画区域的效果。

        如下图所示的边缘增强效果(将原图像与其Canny边缘图像相加)

edge-sharpen


代码

OpenImage.java    (打开图片的界面)

package net.johnhany.shinee;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class OpenImage extends Activity {
	
	private Button btnOpenImage;
	public Bitmap bmp;
	
	private static final int SELECT_PICTURE = 1;
    private String selectedImagePath;
    
	@Override  
    public void onCreate(Bundle savedInstanceState) {
		
        super.onCreate(savedInstanceState);
        
        setContentView(R.layout.activity_open_image);
        
        btnOpenImage = (Button) findViewById(R.id.btn_open_image);

    	addListenerOnButtons();
    }
	
	public void addListenerOnButtons() {
		
		btnOpenImage.setOnClickListener(new OnClickListener() {
 
			@Override
			public void onClick(View arg0) {

                Intent intent = new Intent();
                intent.setType("image/*");
                intent.setAction(Intent.ACTION_GET_CONTENT);
                startActivityForResult(Intent.createChooser(intent, "选择一张图片"), SELECT_PICTURE);
			}
		});
	}
	
	public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK) {
            if (requestCode == SELECT_PICTURE) {
                Uri selectedImageUri = data.getData();
                selectedImagePath = getPath(selectedImageUri);
                Log.i("OpenImage", selectedImagePath);
                
                final Context context = this;
                Intent intent = new Intent(context, TouchProcess.class);
			    intent.putExtra("srcImage", selectedImagePath);
			    startActivity(intent);
            }
        }
    }
	
	public String getPath(Uri uri) {
		
        if(uri == null ) {
            return null;
        }
        
        String res = null;
        String[] proj = { MediaStore.Images.Media.DATA };
        Cursor cursor = getContentResolver().query(uri, proj, null, null, null);
        if(cursor.moveToFirst()){
           int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
           res = cursor.getString(column_index);
        }
        cursor.close();
        return res;
	}
	
	@Override  
    public void onResume() {  
        super.onResume();  
    }
	
}

TouchProcess.java    (主界面)

package net.johnhany.shinee;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import org.opencv.android.BaseLoaderCallback;  
import org.opencv.android.LoaderCallbackInterface;  
import org.opencv.android.OpenCVLoader; 

import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;  
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory; 
import android.graphics.Bitmap.Config;        
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;  
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.SeekBar.OnSeekBarChangeListener;

public class TouchProcess extends Activity{  
	
	private Spinner spinner;
    private ImageView imageView;
    private SeekBar bar;
    private Button btnBack, btnFill, btnSave, btnSaveImage;
    public Bitmap imgSrc, imgProc;
    
    private String logTag = "TouchProcess";
    private String GRAY_BLEND = "彩色灰度融合";
    private String GRAY_SCALE = "灰度级别调整";
    private String EDGE_SHARPEN = "边缘增强";
    private String CONTRAST = "对比度";
    private String BRIGHTNESS = "亮度";
    private String GAUSSIAN_BLUR = "高斯模糊";
    private String UNSHARP_MASK = "反遮罩锐化";
    
    private int[] viewCoords = new int[2];
    private int methodSelected = 1;
    private int methodPara = 0;
    private int dpi;

    private BaseLoaderCallback  mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch (status) {
                case LoaderCallbackInterface.SUCCESS:{
                    System.loadLibrary("opencv_jni");
                    Log.i(logTag, "loaded opencv_jni");
                } break;
                default:{
                    super.onManagerConnected(status);
                    Log.i(logTag, "cannot load opencv_jni");
                } break;
            }
        }
    };
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
    	Log.i(logTag, "onCreate");
        super.onCreate(savedInstanceState);
        
        setContentView(R.layout.activity_touch_process);
        
    	imageView = (ImageView)findViewById(R.id.image_view);
    	btnBack = (Button)findViewById(R.id.btn_back);
    	btnFill = (Button)findViewById(R.id.btn_fill);
    	btnSave = (Button) findViewById(R.id.btn_proc);
    	btnSaveImage = (Button) findViewById(R.id.btn_save);
    	bar = (SeekBar)findViewById(R.id.seek_bar);
        
        loadPicture();
        initSpinner();
    	addListenerOnSpinner();
    	addListenerOnBar();
    	addListenerOnButtons();
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {

		float[] touchPoint = new float[] {event.getX(), event.getY()};
        
        try {
        	switch (event.getAction()) {
        	
        		case MotionEvent.ACTION_DOWN:
        		case MotionEvent.ACTION_MOVE:
        			int shift = 160*dpi/240;
        			touchProc(touchPoint[0]-viewCoords[0], touchPoint[1]-viewCoords[1]-shift);
        			break;
                case MotionEvent.ACTION_UP:   
                	break;
                default:
                	break;
        	}
        }catch(Exception e) {
            e.printStackTrace();
        }
        return true;
	}
    
    public float abs(float in) {
    	if(in < 0)
    		return -in;
    	else
    		return in;
    }
    
    public void touchProc(float tx, float ty) {
    	int w = imgSrc.getWidth();
        int h = imgSrc.getHeight();
        int[] pixels1 = new int[w*h];
        int[] pixels2 = new int[w*h];
        
        Bitmap srcImg = ((BitmapDrawable)imageView.getDrawable()).getBitmap();
        srcImg.getPixels(pixels1, 0, w, 0, 0, w, h);
        imgProc.getPixels(pixels2, 0, w, 0, 0, w, h);
        
        Bitmap dstImg = Bitmap.createBitmap(w, h, Config.ARGB_8888);
        dstImg.setPixels(OpencvJni.combine(pixels1, pixels2, dpi, w, h, tx, ty), 0, w, 0, 0, w, h);
        imageView.setImageBitmap(dstImg);
    }
    
    public void initSpinner() {
    	
		spinner = (Spinner) findViewById(R.id.spinner);
		List<String> list1 = new ArrayList<String>();
    	list1.add(GRAY_BLEND);
    	list1.add(GRAY_SCALE);
    	list1.add(EDGE_SHARPEN);
    	list1.add(CONTRAST);
    	list1.add(BRIGHTNESS);
    	list1.add(GAUSSIAN_BLUR);
    	list1.add(UNSHARP_MASK);
    	
    	ArrayAdapter<String> adapter1 = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, list1);
        adapter1.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinner.setAdapter(adapter1);
        spinner.setPrompt("选择处理方式");
	}
	
	public void addListenerOnSpinner() {

		spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
			
			public void onItemSelected(AdapterView<?> parent, View view, int pos,long id) {
				
				Object method = parent.getItemAtPosition(pos);
				
				if(method.toString().equals(GRAY_BLEND)) {
					methodSelected = 1;
				}else if(method.toString().equals(GRAY_SCALE)) {
					methodSelected = 2;
				}else if(method.toString().equals(EDGE_SHARPEN)) {
					methodSelected = 3;
				}else if(method.toString().equals(CONTRAST)) {
					methodSelected = 4;
				}else if(method.toString().equals(BRIGHTNESS)) {
					methodSelected = 5;
				}else if(method.toString().equals(GAUSSIAN_BLUR)) {
					methodSelected = 6;
				}else if(method.toString().equals(UNSHARP_MASK)) {
					methodSelected = 7;
				}
				
				imgProc = imageProcess(imgSrc, methodSelected, methodPara);
			}
			
			@Override
			public void onNothingSelected(AdapterView<?> arg0) {
				
			}
		});
	}
	
	public void addListenerOnBar() {
		
		bar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
			
        	public void onProgressChanged(SeekBar seekBar, int progress,  boolean fromUser) {
        		
            	methodPara = bar.getProgress();
            	imgProc = imageProcess(imgSrc, methodSelected, methodPara);
            }
              
            public void onStartTrackingTouch(SeekBar seekBar) {  
            }  
   
            public void onStopTrackingTouch(SeekBar seekBar) {  
            }  
        });
	}
	
	public void addListenerOnButtons() {
		
		final Context context = this;
		btnBack.setOnClickListener(new OnClickListener() {
 
			@Override
			public void onClick(View arg0) {
				
				Intent intent = new Intent(context, OpenImage.class);
				intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
				intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			    startActivity(intent);
			}
		});

		btnFill.setOnClickListener(new OnClickListener() {
			
    		@Override
    		public void onClick(View v) {
    			
    			imgProc = imageProcess(imgSrc, methodSelected, methodPara);
    	        imageView.setImageBitmap(imgProc);
    		}
    	});

		btnSave.setOnClickListener(new OnClickListener() {
			
    		@Override
    		public void onClick(View v) {
    	        
    	        imgSrc = ((BitmapDrawable)imageView.getDrawable()).getBitmap();
    	        imgProc = imageProcess(imgSrc, methodSelected, methodPara);
    		}
    	});
    	
		btnSaveImage.setOnClickListener(new OnClickListener() {
 
			@Override
			public void onClick(View arg0) {
				
				Calendar c = Calendar.getInstance();
				int year = c.get(Calendar.YEAR);
				int month = c.get(Calendar.MONTH);
				int day = c.get(Calendar.DAY_OF_MONTH);
				int hour = c.get(Calendar.HOUR_OF_DAY);
				int minute = c.get(Calendar.MINUTE);
				int second = c.get(Calendar.SECOND);
				
				String sdcard = Environment.getExternalStorageDirectory().getPath();
				String filePath = sdcard+"/Shinee-"+year+month+day+hour+minute+second+".jpg";
				Log.i(logTag, filePath);
				File imgFile = new File(filePath);
				if (!imgFile.exists()) {
					try {
						imgFile.createNewFile();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
				
				FileOutputStream out = null;
				try {
					
					out = new FileOutputStream(imgFile);
					imageView.buildDrawingCache();
					Bitmap outImg = imageView.getDrawingCache();
					outImg.compress(Bitmap.CompressFormat.JPEG, 100, out);
					
					Toast.makeText(TouchProcess.this,"结果已保存到 "+filePath,Toast.LENGTH_SHORT).show();
				} catch (Exception e) {
				    e.printStackTrace();
				} finally {
					try{
			        	out.close();
					} catch(Throwable ignore) {
					}
				}
			}
		});
	}
	
	public Bitmap imageProcess(Bitmap srcImg, int method, int para) {
		int w = srcImg.getWidth();
        int h = srcImg.getHeight();
        int[] pixels1 = new int[w*h];
        srcImg.getPixels(pixels1, 0, w, 0, 0, w, h);
        Bitmap dstImg = Bitmap.createBitmap(w, h, Config.ARGB_8888);
        
        switch(method) {
        	case 1:
        		dstImg.setPixels(OpencvJni.grayBlend(pixels1, para, w, h), 0, w, 0, 0, w, h);
        		break;
        	case 2:
        		dstImg.setPixels(OpencvJni.grayScale(pixels1, para,w, h), 0, w, 0, 0, w, h);
        		break;
        	case 3:
        		dstImg.setPixels(OpencvJni.edgeSharpen(pixels1, para, w, h), 0, w, 0, 0, w, h);
        		break;
        	case 4:
        		dstImg.setPixels(OpencvJni.contrast(pixels1, para, w, h), 0, w, 0, 0, w, h);
        		break;
        	case 5:
        		dstImg.setPixels(OpencvJni.brightness(pixels1, para, w, h), 0, w, 0, 0, w, h);
        		break;
        	case 6:
        		dstImg.setPixels(OpencvJni.gaussianBlur(pixels1, para, w, h), 0, w, 0, 0, w, h);
        		break;
        	case 7:
        		dstImg.setPixels(OpencvJni.unsharpMask(pixels1, para, w, h), 0, w, 0, 0, w, h);
        		break;
        	default:
        		break;
        }
        
        return dstImg;
	}
	
	public void loadPicture() {

		Bundle extras = getIntent().getExtras();
		
		String srcImg = extras.getString("srcImage");
		imgSrc = BitmapFactory.decodeFile(srcImg);
		
		dpi = getResources().getDisplayMetrics().densityDpi;
        Log.i("logTag", "DPI: "+dpi);
		
		int w = imgSrc.getWidth();
		int h = imgSrc.getHeight();
		if(w>dpi*2 || h>dpi*2) {
			Toast.makeText(TouchProcess.this, "图片尺寸不能超过 "+dpi*2+"X"+dpi*2, Toast.LENGTH_SHORT).show();
			
			final Context context = this;
			Intent intent = new Intent(context, OpenImage.class);
			intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
			intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		    startActivity(intent);
		}
		
        imageView.setImageBitmap(imgSrc);
        imageView.getLocationOnScreen(viewCoords);
        Log.i(logTag, viewCoords[0]+","+viewCoords[1]);
	}
	
	@Override
    public void onResume() {
		Log.i(logTag, "onResume");
        super.onResume();
        OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_8, this, mLoaderCallback);
    }
}

activity_touch_process.xml    (主界面UI)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:opencv="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    <LinearLayout 
        android:orientation="vertical"
    	android:layout_width="match_parent"
    	android:layout_height="match_parent" 
    	android:layout_weight="1"
        >
        
        <Spinner
        	android:id="@+id/spinner"
        	android:layout_width="match_parent"
        	android:layout_height="wrap_content"
        	android:prompt="@string/str_spinner" />
    
    	<SeekBar  
        	android:id="@+id/seek_bar"  
        	android:layout_width="300dp"  
        	android:layout_height="30dp"  
        	android:max="100" 
        	android:progress="0" />
    
    	<ImageView
        	android:id="@+id/image_view"
        	android:layout_width="wrap_content"
        	android:layout_height="wrap_content"
        	android:contentDescription="@string/str_image_view" />
    	
    	</LinearLayout>
    
   		
    	<LinearLayout 
    	    android:orientation="horizontal"
    		android:layout_width="match_parent"
    		android:layout_height="match_parent" 
    		android:layout_weight="4"
			>
    	    
    	    <Button
				android:id="@+id/btn_proc"
				android:layout_width="160sp"
				android:layout_height="wrap_content"
				android:text="@string/str_btn_proc" />

    		<Button
        		android:id="@+id/btn_fill"
        		android:layout_width="160sp"
        		android:layout_height="wrap_content"
        		android:text="@string/str_btn_fill" />
    		
    		</LinearLayout>
    
    	
    	<LinearLayout 
    	    android:orientation="horizontal"
    		android:layout_width="match_parent"
    		android:layout_height="match_parent" 
    		android:layout_weight="4"
			>
			
			<Button
        		android:id="@+id/btn_back"
        		android:layout_width="160sp"
        		android:layout_height="wrap_content"
        		android:text="@string/str_btn_back" />
		
			<Button
				android:id="@+id/btn_save"
				android:layout_width="160sp"
				android:layout_height="wrap_content"
				android:text="@string/str_btn_save" />
				
    	</LinearLayout>
</LinearLayout> 

OpencvJni.java    (声明JNI接口)

package net.johnhany.shinee;  
  
public class OpencvJni {  
    public static native int[] grayBlend(int[] pixels, int para, int w, int h);
    public static native int[] grayScale(int[] pixels, int para, int w, int h);
    public static native int[] edgeSharpen(int[] pixels, int para, int w, int h);
    public static native int[] contrast(int[] pixels, int para, int w, int h);
    public static native int[] brightness(int[] pixels, int para, int w, int h);
    public static native int[] gaussianBlur(int[] pixels, int para, int w, int h);
    public static native int[] unsharpMask(int[] pixels, int para, int w, int h);
    public static native int[] touchRegion(int[] pixels, int w, int h, float cx, float cy);
    public static native int[] combine(int[] pixels1, int[] pixels2, int dpi, int w, int h, float cx, float cy);
}  

OpencvJni.cpp    (部分,通过JNI调用的C函数)

#include <OpencvJni.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <string>
#include <vector>
#include <math.h>

#define MAX_CHANNEL 3
#define MAX_CORE_X 11
#define MAX_CORE_Y 11
#define MAX_CORE_LENGTH MAX_CORE_X*MAX_CORE_Y
#define PI 3.1415926
#define E 2.7182818

using namespace cv;
using namespace std;

JNIEXPORT jintArray JNICALL Java_net_johnhany_shinee_OpencvJni_edgeSharpen(JNIEnv* env, jclass obj, jintArray buf, jint para, jint w, jint h)
{
    jint *cbuf, *dbuf;
    cbuf = env->GetIntArrayElements(buf, (jboolean*) false);
    if(cbuf == NULL) {
        return 0;
    }

    Mat srcImg(h, w, CV_8UC4, (unsigned char*)cbuf);
    Mat grayImg(h, w, CV_8UC1);
    Mat dstImg(h, w, CV_8UC4);

    if(para < 2)	para = 2;
    int lowThreshold = para / 2;
    int ratio = 3;

    cvtColor(srcImg, grayImg, CV_RGBA2GRAY);
    blur(grayImg, grayImg, Size(3,3));
    Canny(grayImg, grayImg, lowThreshold, lowThreshold*ratio, 3);
    cvtColor(grayImg, dstImg, CV_GRAY2RGBA);
    add(srcImg, dstImg, dstImg);

    dbuf = (jint*)dstImg.ptr(0);
    int size=w * h;
    jintArray result = env->NewIntArray(size);
    env->SetIntArrayRegion(result, 0, size, dbuf);
    env->ReleaseIntArrayElements(buf, cbuf, 0);
    return result;
}

JNIEXPORT jintArray JNICALL Java_net_johnhany_shinee_OpencvJni_combine(JNIEnv* env, jclass obj, jintArray img1, jintArray img2, jint dpi, jint w, jint h, jfloat cx, jfloat cy)
{
	jint *buf1, *buf2;
	buf1 = env->GetIntArrayElements(img1, (jboolean*) false);
	buf2 = env->GetIntArrayElements(img2, (jboolean*) false);
	if(buf1==NULL || buf2==NULL) {
		return 0;
	}

	Mat srcImg(h, w, CV_8UC4, (unsigned char*)buf1);
	Mat dstImg(h, w, CV_8UC4, (unsigned char*)buf2);

	float radius = 56.0;
	radius *= dpi/240;
	float dist, alpha, beta;
	float radius2 = radius*radius;
	for(int y=0; y<srcImg.rows; y++) {
		for(int x=0; x<srcImg.cols; x++) {
			dist = (float)((x-cx)*(x-cx) + (y-cy)*(y-cy));
			if(dist <= radius2) {
				alpha = dist / radius2;
				beta = 1.0 - alpha;
				srcImg.at<Vec4b>(y,x)[0] = srcImg.at(y,x)[0]*alpha + dstImg.at(y,x)[0]*beta;
				srcImg.at<Vec4b>(y,x)[1] = srcImg.at(y,x)[1]*alpha + dstImg.at(y,x)[1]*beta;
				srcImg.at<Vec4b>(y,x)[2] = srcImg.at(y,x)[2]*alpha + dstImg.at(y,x)[2]*beta;
				//srcImg.at<Vec4b>(y,x)[3] = srcImg.at(y,x)[3]*alpha + dstImg.at(y,x)[3]*beta;
			}
		}
	}

	int size = w*h;
	jintArray result = env->NewIntArray(size);
	env->SetIntArrayRegion(result, 0, size, buf1);
	env->ReleaseIntArrayElements(img1, buf1, 0);
	env->ReleaseIntArrayElements(img2, buf2, 0);
	return result;
}

4

avatar
2 Comment threads
2 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
3 Comment authors
John Hanyskjexissky Recent comment authors
  Subscribe  
最新 最旧
订阅评论
skjexis
skjexis

博主你好~我是一个android的初学者。目前在做一个基于Opencv的图像处理APP,但是感觉中文资料稀少一直不知所措。看到你的手指抹画APP后觉得大有启发,请问你能分享一下完整版的Opencvjni.cpp吗?

sky
sky

楼主,你好,能否将工程发我一份体验学习下,谢谢~