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

AndroidVision: 在您的手机上学习图像处理

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (20投票s)

2014年1月6日

CPOL

7分钟阅读

viewsIcon

82510

downloadIcon

2847

尝试在您的手机上使用 OpenCV 图像处理方法。

目录 

演示

引言

本文介绍了一个 Android 应用程序,演示了 OpenCV 平台在 Android 上的功能。其主要目标不是执行速度,而是实现简易性。毕竟,我想演示使用特定滤镜的结果,因此没有进行优化。

一如既往:我是无辜的

这是“立即发布”版本。这意味着基础设施已经存在,但支持的方法和配置是基本的。然而,积极地看待它:这也意味着有改进的空间!

你需要什么?

尽管我使用了三个库,但你只需要下载一个:OpenCV 库。我使用的是 2.4.5 版本。其他两个库包含在源代码下载中。它们是我自己的 触摸 DSL对象编辑器。它们在此应用程序中的使用将进行解释,但要了解其内部工作原理,你必须阅读这些文章。

所有代码都是基于 Android 3.0 编写的。

Activity 和捕获摄像头

结构

启动 Activity 是 AndroidVision Activity,它主要基于 OpenCV 教程 2 - Android 混合处理示例代码中的 Tutorial2Activity Activity。视图 FrameGrabberView 也大部分是 OpenCV 库中 CameraBridgeViewBase 的副本。所做的更改实际上是在 FrameGrabberViewBase 中,我向其中添加了注册 FrameGrabberViewBase.CvCameraViewDrawable 的功能,允许您在相机视图的顶部绘制内容。

AndroidVision Activity 的一项更改是它现在实现了三个菜单选项

  1. 选择:允许您选择图像处理方法。
  2. 配置:允许您配置所选方法。
  3. 显示:在屏幕上显示方法的配置。

代码

AndroidVision

Activity 的基本功能是

  • 创建一个目前仅支持后置摄像头的相机视图
  • 注册 CvCameraViewListener2 事件
  • 初始化 OpenCV 库,然后初始化相机视图
  • 注册 FrameGrabberViewBase.CvCameraViewDrawable 事件
  • 应用选定的 IImageProcessingMethod

相机视图的创建在 AndroidVision Activity 的 OnCreate 方法中完成

@Override
public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	
	// More code ...
	
	View cameraView = CreateBackFacingCameraView();
	cameraView.setOnTouchListener(this);
	
	setContentView(cameraView);       

}

private View CreateBackFacingCameraView()
{
	LayoutInflater inf = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
	
	View cameraView = inf.inflate(R.layout.framegrabber_view, null);
	
	mCameraFacingBackView = (FrameGrabberViewBase)cameraView.findViewById(R.id.fgView);
	
	int cameraCount = 0;

	CameraInfo cameraInfo = new CameraInfo();
	cameraCount = Camera.getNumberOfCameras();
	int cameraIndex = 0;
	for ( int camIdx = 0; camIdx < cameraCount; camIdx++ ) {
		Camera.getCameraInfo( camIdx, cameraInfo );
		if ( cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK  ) {
			cameraIndex = camIdx;
			break;
		}
	}
	
	mCameraFacingBackView.setCameraIndex(cameraIndex);
	
	mCameraFacingBackView.setVisibility(SurfaceView.VISIBLE);
	mCameraFacingBackView.setCvCameraViewListener(this);
	
	return cameraView;
}

注册 FrameGrabberViewBase.CvCameraViewListener2 事件(它提供执行处理的帧)在创建后置摄像头视图期间完成,请参阅 CreateBackFacingCameraView()

这是 FrameGrabberViewBase.CvCameraViewListener2 接口

public abstract class FrameGrabberViewBase extends SurfaceView implements SurfaceHolder.Callback 
{
	// More code...
	
	public interface CvCameraViewListener2 {
		public void onCameraViewStarted(int width, int height);
		public void onCameraViewStopped();
		public Mat onCameraFrame(CvCameraViewFrame inputFrame);
	};
	
	// More code
}

OpenCV 的初始化在 OnResume 中完成,它注册了一个回调,该回调在 OpenCV 初始化后初始化视图。

private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
	@Override
	public void onManagerConnected(int status) {
		switch (status) {
			case LoaderCallbackInterface.SUCCESS:
			{            		
				Log.i(TAG, "OpenCV loaded successfully");
				InitializeView();
			} break;
			default:
			{
				super.onManagerConnected(status);
			} break;
		}
	}
};
    
@Override
public void onResume()
{
	super.onResume();
	OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_3, this, mLoaderCallback);
}

private void InitializeView()
{
	InitializeBackfacingCameraView();			
	
	ApplyMethodOnCurentView();
}

private void InitializeBackfacingCameraView()
{
	if (mCameraFacingBackView != null)
	{	
		mCameraFacingBackView.setCameraViewDrawable(overlayList);
		mCameraFacingBackView.enableView();
	}
}

注册 FramegrabberViewFrameGrabberViewBase.CvCameraViewDrawable 事件(它允许您在视图的 Canvas 上绘图)在 InitializeBackfacingCameraView 中完成,其代码已在上面显示。

这是接口

public abstract class FrameGrabberViewBase extends SurfaceView implements SurfaceHolder.Callback
{
	// More code...
	
    public interface CvCameraViewDrawable {
    	public void setResolution(int width, int height);   	
    	public void draw(Canvas canvas);
    }
	
	// More code
}

Framegrabberview 中绘制内容的有两个对象

  1. ImageProcessingMethod 可以绘制其配置设置
  2. ObjectEditorCameraViewDrawable 可以绘制控件以编辑配置设置

它们具体做什么将在后面解释。

两者都通过 CameraViewDrawableList 抽象

public class CameraViewDrawableList implements FrameGrabberViewBase.CvCameraViewDrawable {

	public void setImageMethodOverlay(FrameGrabberViewBase.CvCameraViewDrawable imageMethod)
	{
		if(Contains(this.imageMethod))
		{
			Remove(this.imageMethod);
		}
		this.imageMethod = imageMethod;
		Add(this.imageMethod);
	}

	public void setObjectEditorOverlay(FrameGrabberViewBase.CvCameraViewDrawable objectEditor)
	{
		if(Contains(objectEditor))
		{
			Remove(objectEditor);
		}
		this.objectEditor = objectEditor;
		Add(this.objectEditor);
	}
	
	private void Add(FrameGrabberViewBase.CvCameraViewDrawable item)
	{
		if(width != 0 && height != 0)
		{
			item.setResolution(width, height);
		}
		
		cameraViewDrawableList.add(item);
	}
	
	private void Remove(FrameGrabberViewBase.CvCameraViewDrawable item)
	{
		cameraViewDrawableList.remove(item);
	}
	
	private boolean Contains(FrameGrabberViewBase.CvCameraViewDrawable item)
	{
		return cameraViewDrawableList.contains(item);
	}

	@Override
	public void setResolution(int width, int height) {
		this.width = width;
		this.height = height;
		
		for(FrameGrabberViewBase.CvCameraViewDrawable item : cameraViewDrawableList)
		{
			item.setResolution(width, height);
		}
	}

	@Override
	public void draw(Canvas canvas) {
		if(objectEditor != null)
			objectEditor.draw(canvas);
		if(imageMethod != null)
			imageMethod.draw(canvas);
	}
	
	private int width = 0;
	private int height = 0;
	
	private FrameGrabberViewBase.CvCameraViewDrawable imageMethod;
	private FrameGrabberViewBase.CvCameraViewDrawable objectEditor;
	private ArrayList<FrameGrabberViewBase.CvCameraViewDrawable> cameraViewDrawableList = new ArrayList<FrameGrabberViewBase.CvCameraViewDrawable>();
}

最后,选定的 IImageProcessingMethodonCameraFrame 回调方法中应用:(请参阅 FrameGrabberViewBase.CvCameraViewListener2 接口)

@Override
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
	mInputMat = inputFrame.rgba();
	return ApplyMethod(mInputMat);
}

private Mat ApplyMethod(Mat mRgba)
{
	if(mMethod != null && mRgba != null)
	{
		Mat sourceImage = new Mat();
		
		// Some methods only apply to a grayscale image, so we must first convert out RGBA image
		if(mMethod.getInputImageType() == ImageType.Gray)
		{
			Imgproc.cvtColor(mRgba, sourceImage, Imgproc.COLOR_BGRA2GRAY, 4);
		}
		else
		{
			sourceImage = mRgba;
		}
		
		if(mResultMat == null)
		{
			mResultMat = new Mat();
		}
		
		mMethod.setInputImage(sourceImage);
		mMethod.setOutputImage(mResultMat);
		
		mMethod.Execute();
		
		mResultMat = mMethod.getOutputImage();
	}

	return mResultMat;
}

FrameGrabberViewBase

以下代码仅显示了允许在相机视图顶部绘图的部分。

public abstract class FrameGrabberViewBase extends SurfaceView implements SurfaceHolder.Callback 
{
	// More methods ...
	
    protected void deliverAndDrawFrame(CvCameraViewFrame frame) {
		// More code which calls onCameraFrame, 
		//	converts the returned Mat into a Bitmap 
		//	and draws it in the surfaceholder
	
				// Perform your custom drawing in the Canvas of the surfaceholder
				// This drawing is done after the the Bitmap with the OnCameraFrame result is drawn, 
				//	thus resulting in us drawing on top of the edited image
                if (mCameraViewDrawable != null) {
                    mCameraViewDrawable.draw(canvas);
                }
	}
	
	// More methods ...
}

选择、配置和应用方法

结构

支持的方法名称存储在 AvailableImageProcessingMethods 类中,该类还对方法进行分类。选择发生在 ImageProcessingMethodSelection Activity 中,该 Activity 具有 ExpandableListView 作为视图。通过向 AvailableImageMethodAdapter 提供一个 AvailableImageProcessingMethods 类型的对象,我们可以获得可用方法的分类视图。

选择方法后,也可以对其进行配置。这在 ImageProcessingMethodConfiguration 中完成,该配置具有一个 ObjectEditorView 用于编辑配置属性。在编辑配置属性期间,您还可以选择要在相机视图中可编辑的属性。这将在下一节中解释。

最后,选定的 IImageProcessingMethod 应用于相机提供的帧。

代码

ImageProcessingMethodSelection

public class ImageProcessingMethodSelection extends Activity implements OnChildClickListener, OnGroupClickListener {
	private static String SELECTED_METHOD = "METHODSELECTOR_SELECTED_METHOD";
	
	AvailableImageMethodAdapter adapter;
	ExpandableListView availableImageMethodView;
 
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.imagemethod_selection);

		adapter = new AvailableImageMethodAdapter(this, new AvailableImageProcessingMethods());

		availableImageMethodView = (ExpandableListView) findViewById(R.id.exlstImgMethSelect);
		availableImageMethodView.setAdapter(adapter);
		availableImageMethodView.setGroupIndicator(null);
		availableImageMethodView.setOnChildClickListener(this);
		availableImageMethodView.setOnGroupClickListener(this);

		int numberOfGroups = adapter.getGroupCount();
		for (int i = 0; i < numberOfGroups; i++)
		{
			availableImageMethodView.expandGroup(i);
		}
	}

	public static String getSelectedMethod(Bundle bundle)
	{
		if(!bundle.containsKey(SELECTED_METHOD))
		{
			return null;
		}
		return bundle.getString(SELECTED_METHOD);
	}
	
	public static void setSelectedMethod(Bundle bundle, String selectedMethod)
	{
		bundle.putString(SELECTED_METHOD, selectedMethod);
	}

	@Override
	public boolean onChildClick(ExpandableListView parent, View v,
			int groupPosition, int childPosition, long id) {
    	String selectedMethodId = (String)adapter.getChild(groupPosition, childPosition);
	    Intent result = new Intent();
	    Bundle bundle = new Bundle();
	    setSelectedMethod(bundle, selectedMethodId);
	    result.putExtras(bundle);

	    setResult(Activity.RESULT_OK, result);
	    
	    super.onBackPressed();
        return false;
	}

	@Override
	public boolean onGroupClick(ExpandableListView arg0, View arg1, int arg2,
			long arg3) {
		// prevent the group from collapsing
		return true;
	}
}

如您所见,可用方法在 AvailableImageProcessingMethods 类中抽象

public class AvailableImageProcessingMethods {
	private ArrayList<String> availableCategories = new ArrayList<String>();
	private Map<String, ArrayList<String>> categoriesMethodIdMap = new Hashtable<String, ArrayList<String>>(); 
	private Map<String, IImageProcessingMethod> methodIdToMethodMap = new Hashtable<String, IImageProcessingMethod>(); 
	private Map<String, String> methodIdToDisplayNameMap = new Hashtable<String, String>(); 
	
	public AvailableImageProcessingMethods()
	{
		RegisterMethod("default", "None", new NoOpMethod());
		RegisterMethod("blur", "Box", new BoxBlur());
		RegisterMethod("blur", "Gaussian", new GaussianBlur());
		RegisterMethod("blur", "Median", new MedianBlur());
		RegisterMethod("edge detection", "Canny", new CannyEdgeDetection());
		RegisterMethod("edge detection", "Sobel", new SobelEdgeDetection());
		RegisterMethod("hough transform", "Lines", new HoughLines());
		RegisterMethod("hough transform", "Circles", new HoughCircles());
		RegisterMethod("mathematical morph.", "Erosion", new ErosionMathMorph());
		RegisterMethod("mathematical morph.", "Dilation", new DilationMathMorph());
		RegisterMethod("various", "Histigram Eq.", new HistogramEqualization());
	}
	
	public ArrayList<String> getAvailableCategories()
	{
		return availableCategories;
	}
	
	public String getCategory(int index)
	{
		return availableCategories.get(index);
	}
	
	public ArrayList<String> getAvailableMethodIds(String category) {
		return categoriesMethodIdMap.get(category);
	}
	
	public IImageProcessingMethod getMethod(String methodId)
	{
		if(!methodIdToMethodMap.containsKey(methodId))
		{
			return null;
		}
		return methodIdToMethodMap.get(methodId);
	}
	
	public String getMethodDisplayname(String methodId)
	{
		return methodIdToDisplayNameMap.get(methodId);
	}
	
	private void RegisterMethod(String category, String displayName, IImageProcessingMethod method)
	{
		if(!availableCategories.contains(category))
		{
			availableCategories.add(category);
		}
		if(!categoriesMethodIdMap.containsKey(category))
		{
			categoriesMethodIdMap.put(category, new ArrayList<String>());
		}
		categoriesMethodIdMap.get(category).add(method.getMethodId());
		methodIdToMethodMap.put(method.getMethodId(), method);
		methodIdToDisplayNameMap.put(method.getMethodId(), displayName);
	}
}

AvailableImageMethodAdapter 扩展了 BaseExpandableListAdapter,因此可用方法显示为可扩展列表

public class AvailableImageMethodAdapter extends BaseExpandableListAdapter {

	// More code ...
	
	@Override
	public View getChildView(int groupPosition, int childPosition,
			boolean isLastChild, View convertView, ViewGroup parent) {
		
        String imgMethodId = (String) getChild(groupPosition, childPosition);
        
        if (convertView == null) {
            LayoutInflater infalInflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = infalInflater.inflate(R.layout.imagemethod_item, null);
        }
 
        TextView tvImageMethodId = (TextView)convertView.findViewById(R.id.tvImgMethItem); 
        tvImageMethodId.setText(availableMethods.getMethodDisplayname(imgMethodId));
        
        return convertView;
	}

	// More code ...

	@Override
	public View getGroupView(int groupPosition, boolean isExpanded,
			View convertView, ViewGroup parent) {
		
		String category = (String) getGroup(groupPosition);
		
		if (convertView == null) {
			LayoutInflater inf = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			convertView = inf.inflate(R.layout.imagemethod_group, null);
		}
		
		TextView tvCategory = (TextView)convertView.findViewById(R.id.tvImgMethGrp);
		tvCategory.setText(category);

		return convertView;
	}
	
	// More code ...
	
}

启动 ImageProcessingMethodSelection activity 并处理其结果在 AndroidVision 中完成

private void handleSelectMethod()
{
	Intent myIntent = new Intent(AndroidVision.this, ImageProcessingMethodSelection.class);
	startActivityForResult(myIntent, REQUEST_METHODSELECTOR);
}

private void handleSelectMethodResult(int requestCode, int resultCode, Intent intent)
{
	if(intent == null)
	{
		return;
	}
	
	String requestedMethodId = ImageProcessingMethodSelection.getSelectedMethod(intent.getExtras());
	
	if(requestedMethodId == null || requestedMethodId.length() == 0)
	{
		return;
	}
	selectMethod(requestedMethodId);

}

private void selectMethod(String methodId)
{
	IImageProcessingMethod requestedMethod = availableMethods.getMethod(methodId);
	if(requestedMethod == null)
	{
		return;
	}
	
	mMethod = requestedMethod;

	currentMethod = methodId;
	
	overlayList.setImageMethodOverlay(mMethod);
	
	ApplyConfigurationOnObjectEditor();
	
	ApplyMethodOnCurentView();		
}

private void ApplyConfigurationOnObjectEditor()
{
	if(mMethod == null)
	{
		return;
	}
	
	IImageProcessingMethodConfiguration configuration = mMethod.getConfiguration();
	
	ArrayList<PropertyProxy> inViewEditableProps = new ArrayList<PropertyProxy>();
	if(configuration != null)
	{
		for(String propertyName : configuration.availablePropertyList())
		{
			inViewEditableProps.add(PropertyProxy.CreateFomPopertyName(propertyName, configuration, converterRegistry));
		}
	}
	
	propertyEditorOverlay.setPropertyList(inViewEditableProps);
}

ImageProcessingMethodConfiguration

配置使用我的 对象编辑器 库完成。如您在 onCreate 方法中所示,显示编辑器的框架是定制的,允许在右上角提供一个小链接图标。使用此链接,可以在相机视图中直接编辑属性。通过注册对象编辑器的创建事件,我们可以将点击处理程序附加到这些图标。

public class ImageProcessingMethodConfiguration extends Activity implements ItemCreationCallback, OnClickListener {
	public  static String CONFIGURATION = "METHODCONFIGURATION_CONFIGURATION";

	private ObjectProxy objectProxy;
	private ObjectEditorView view;
	private IImageProcessingMethodConfiguration objectToEdit = null;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		ObjectEditorViewConfig config = new ObjectEditorViewConfig();
		config.FrameId = R.layout.imageproperty_frame;
		config.FrameNameId = R.id.tvPropertyName;
		config.FramePlaceholderId = R.id.llPropertyViewHolder;

		config.FrameDlgId = R.layout.imageproperty_external_frame;
		config.FrameDlgNameId = R.id.tvPropertyNameExt;
		config.FrameDlgPlaceholderId = R.id.llPropertyViewHolderExt;

		config.FrameIntentId = R.layout.imageproperty_external_frame;
		config.FrameIntentNameId = R.id.tvPropertyNameExt;
		config.FrameIntentPlaceholderId = R.id.llPropertyViewHolderExt;

		view = new ObjectEditorView(this, config);

		view.setItemCreationCallback(this);

		Bundle b = getIntent().getExtras(); 
		objectToEdit = (IImageProcessingMethodConfiguration)b.getParcelable(CONFIGURATION); 

		TypeConverterRegistry converterRegistry = TypeConverterRegistry.create();
		objectProxy = new ObjectProxy(objectToEdit, converterRegistry);
		view.setObjectToEdit(objectProxy);

		setContentView(view);
	}


	@Override
	public void onBackPressed() {
		Intent result = new Intent();
		result.putExtra(ImageProcessingMethodConfiguration.CONFIGURATION, (Parcelable)objectToEdit);

		setResult(Activity.RESULT_OK, result);

		super.onBackPressed();
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();

		objectProxy.Destroy();
		objectProxy = null;
	}

	@Override
	public void OnFrameCreated(View frame) {
		View imageFrame = frame.findViewById(R.id.ivPropertyConnected);
		if(imageFrame != null)
		{
			imageFrame.setOnClickListener(this);
			return;
		}
		View imageFrameExt = frame.findViewById(R.id.ivPropertyConnectedExt);
		if(imageFrameExt != null)
		{
			imageFrameExt.setOnClickListener(this);
			return;
		}
	}

	@Override
	public void OnFrameInitialized(View frame, PropertyProxy property) {
		ImageView imageFrame = (ImageView)frame.findViewById(R.id.ivPropertyConnected);
		String propertyName = property.getName();
		if(imageFrame == null)
		{
			imageFrame = (ImageView)frame.findViewById(R.id.ivPropertyConnectedExt);
		}
		if(imageFrame != null)
		{
			if(objectToEdit.isPropertyAllowedInView(propertyName))
			{
				setEditInViewIcon(imageFrame, propertyName);
				imageFrame.setTag(property.getName());
			}
			return;
		}
	}

	@Override
	public void OnFrameUnInitialized(View frame) {
		View imageFrame = frame.findViewById(R.id.ivPropertyConnected);
		if(imageFrame == null)
		{
			imageFrame = frame.findViewById(R.id.ivPropertyConnectedExt);
		}
		if(imageFrame != null)
		{
			imageFrame.setTag(null);
			return;
		}
	}

	@Override
	public void onClick(View view) {
		ImageView imageFrame = (ImageView)view;
		if (imageFrame != null)
		{
			String propertyName = (String)imageFrame.getTag();
			toggleEditInView(propertyName);
			setEditInViewIcon(imageFrame, propertyName);
		}
	}
	
	private void toggleEditInView(String propertyName)
	{
		if(objectToEdit.isPropertyAvailableInView(propertyName))
		{
			objectToEdit.removePropertyToAvailableInView(propertyName);
		}
		else
		{
			objectToEdit.addPropertyToAvailableInView(propertyName);
		}
	}
	
	private void setEditInViewIcon(ImageView imageFrame, String propertyName)
	{
		if(objectToEdit != null && propertyName != null)
		{
			if(objectToEdit.isPropertyAvailableInView(propertyName))
			{
				imageFrame.setImageResource(R.drawable.connected);
			}
			else
			{
				imageFrame.setImageResource(R.drawable.disconnected);
			}
		}
	}
}

配置对象通过 parcelables 传递给编辑器。为此,所有配置类都实现了 Parcelable

public class BoxConfiguration extends ImageProcessingMethodConfigBase implements Parcelable, IImageProcessingMethodConfiguration   {

	int kernelSize = 1;
	
	public BoxConfiguration()
	{
		fillInViewEditingAllowed(this);
	}
	
	public BoxConfiguration(Parcel in)
	{
		fillInViewEditingAllowed(this);
		readFromParcel(in);
	}
	
	// More methods unimportant to the Parcelable implementation
	
	@Override
	public int describeContents() {
		return 0;
	}
	
	public void readFromParcel(Parcel in) {
		baseReadFromParcel(in);
		kernelSize = in.readInt();
	}

	@Override
	public void writeToParcel(Parcel arg0, int arg1) {
		baseWriteToParcel(arg0, arg1);
		arg0.writeInt(kernelSize);

	}
	
	public static final Parcelable.Creator<BoxConfiguration> CREATOR = new Parcelable.Creator<BoxConfiguration>()
	{
		@Override
		public BoxConfiguration createFromParcel(Parcel source) {
			return new BoxConfiguration(source);
		}

		@Override
		public BoxConfiguration[] newArray(int size) {
			return new BoxConfiguration[size];
		}
	};
}

启动 ImageProcessingMethodConfiguration activity 并处理其结果在 AndroidVision 中完成

private void handleConfigMethod()
{
	if(mMethod == null)
	{
		return;
	}

	IImageProcessingMethodConfiguration configuration = mMethod.getConfiguration();
	if(configuration == null)
	{
		Toast.makeText(this, "No configuration available", Toast.LENGTH_SHORT).show();;
		return;
	}
	Intent myIntent = new Intent(AndroidVision.this, ImageProcessingMethodConfiguration.class);			
	myIntent.putExtra(ImageProcessingMethodConfiguration.CONFIGURATION, (Parcelable)configuration);
	
	startActivityForResult(myIntent, REQUEST_METHODCONFIGURATION);
}

private void handleConfigMethodResult(int requestCode, int resultCode, Intent intent)
{
	IImageProcessingMethodConfiguration configuration = (IImageProcessingMethodConfiguration)intent.getExtras().getParcelable(ImageProcessingMethodConfiguration.CONFIGURATION);
	mMethod.setConfiguration(configuration);
	
	ApplyConfigurationOnObjectEditor();
}

private void ApplyConfigurationOnObjectEditor()
{
	//See above for the implementation
}

ImageProcessingMethodBase

所有图像处理方法都派生自 ImageProcessingMethodBase 并实现 IImageProcessingMethod 接口

public interface IImageProcessingMethod extends FrameGrabberViewBase.CvCameraViewDrawable {
	
	String getMethodId();
	
	boolean ToggleShowExtData();
	
	ImageType getInputImageType();
	ImageType getOutputImageType();
	
	Mat getInputImage();
	void setInputImage(Mat image);
	Mat getOutputImage();
	void setOutputImage(Mat image);
	
	IImageProcessingMethodConfiguration getConfiguration();
	void setConfiguration(IImageProcessingMethodConfiguration config);
	
	void Execute();
}
public abstract class ImageProcessingMethodBase implements IImageProcessingMethod {

	protected boolean showExtData = false;	
	
	public ImageProcessingMethodBase()
	{
		redPaint = new Paint();
		redPaint.setColor(Color.RED);
        redPaint.setTextSize(15);
		
        greenPaint = new Paint();
		greenPaint.setColor(Color.GREEN);
		greenPaint.setTextSize(15);
	}

	@Override
	public boolean ToggleShowExtData() {
		showExtData = !showExtData;
		return showExtData;
	}
	
	protected Paint redPaint;
	protected Paint greenPaint;
}

一个示例是 BoxBlur

public class BoxBlur extends ImageProcessingMethodBase {

	public static final String METHOD_ID = "BOXBLUR";
	
	private Size sz;
	
	public BoxBlur()
	{
		configuration = new BoxConfiguration();
		configuration.setKernelSize(5);
		
		CopyConfiguration();
	}

	@Override
	public String getMethodId() {
		return METHOD_ID;
	}

	@Override
	public void setResolution(int width, int height) {
	}

	@Override
	public void draw(Canvas canvas) {
		if(!showExtData)
		{
			return;
		}
		
		CanvasTextPage page = new CanvasTextPage(canvas);
		page.startNewPage();
		page.drawTextLine(METHOD_ID, redPaint);
		page.drawTextLine("Kernel size: " + sz.width,  configuration.isPropertyAvailableInView("KernelSize") ? greenPaint : redPaint);

	}

	@Override
	public ImageType getInputImageType() {
		return ImageType.Gray;
	}

	@Override
	public ImageType getOutputImageType() {
		return ImageType.Gray;
	}

	@Override
	public Mat getInputImage() {
		return inputImage;
	}

	@Override
	public void setInputImage(Mat image) {
		inputImage = image;
	}

	@Override
	public Mat getOutputImage() {
		return outputImage;
	}

	@Override
	public void setOutputImage(Mat image) {
		outputImage = image;
	}

	@Override
	public IImageProcessingMethodConfiguration getConfiguration() {
		return configuration;
	}

	@Override
	public void setConfiguration(IImageProcessingMethodConfiguration config) {
		configuration = (BoxConfiguration)config;
	}

	@Override
	public void Execute() {
		CopyConfiguration(); 
    	Imgproc.blur(inputImage, outputImage, sz);
	}
	
	private void CopyConfiguration()
	{
		sz = new Size(configuration.getKernelSize(), configuration.getKernelSize()); 
	}

	BoxConfiguration configuration;
	Mat inputImage;
	Mat outputImage;
	
}

方法的应用发生在 AndroidVision.onCameraFrame 中,其代码已在上面显示。

在相机视图中直接配置编辑

结构

我们的想法是在相机视图的顶部有一个对象编辑器。这允许您更改方法的配置并立即看到结果。

在接下来的文本中,我将频繁使用“控件”、“列表视图”等词……不要被这些词误导:它们与 Android 提供的常规控件无关。相反,它们是一组模仿一些标准控件但提供自定义绘图和触摸处理的类。它们都派生自 CanvasWidgets

基于这些派生自 CanvasWidgets 的类,然后是一系列实现类似于我的 对象编辑器 的属性编辑器的类,尽管要简单得多。

以下是一个简单的方案,展示了所涉及的内容


代码

CanvasWidgetBase

基类实现了一些基本功能,例如定位和设置颜色。

public abstract class CanvasWidgetBase {

	public void setWidth(int width)
	{
		this.width = width;
		PositionChanged();
	}
	
	public int getWidth()
	{
		return this.width;
	}

	public void setHeight(int height)
	{
		this.height = height;
		PositionChanged();
	}
	
	public int getHeigth()
	{
		return this.height;
	}

	public void setX(int x)
	{
		this.x = x;
		PositionChanged();
	}
	
	public int getX()
	{
		return this.x;
	}

	public void setY(int y)
	{
		this.y = y;
		PositionChanged();
	}
	
	public int getY()
	{
		return this.y;
	}
	
	public int getColor() {
		return color;
	}

	public void setColor(int color) {
		this.color = color;
	}
	
	public void PositionChanged()
	{
		
	}
	
	public abstract void Draw(Canvas canvas);
    
    public abstract boolean onTouchEvent(MotionEvent motion); 

	protected int width;
	protected int height;
	protected int x;
	protected int y;
	
	protected int color = Color.BLUE;

	protected Paint paint = new Paint();
}

由此派生出以下类

  • ButtonCanvasWidget:实现一个按钮
  • ItemsListCanvasWidget:实现一个 CanvasWidgetBase 控件的垂直列表
  • ListItemSelectorCanvasWidget:实现一个允许水平滚动列表的控件
  • SliderCanvasWidget:实现一个滑块

下面显示了 ListItemSelectorCanvasWidget 的代码

public class ListItemSelectorCanvasWidget extends CanvasWidgetBase {

	public interface OnSelectHandler
	{
		void OnNext(ListItemSelectorCanvasWidget listItemSelectorCanvasWidget);
		void OnPrevious(ListItemSelectorCanvasWidget listItemSelectorCanvasWidget);
	}
	
	public ListItemSelectorCanvasWidget()
	{
		ChangeSelectedItemGesture clickGestureBuilder = new ChangeSelectedItemGesture(this);
		
		touchHandler = new TouchHandler();
		touchHandler.addGesture(clickGestureBuilder.create());
	}
	
	public void setOnSelectHandler(OnSelectHandler onSelectHandler)
	{
		this.onSelectHandler = onSelectHandler;
	}
	
	public OnSelectHandler getOnSelectHandler()
	{
		return this.onSelectHandler;
	}
	
	@Override
	public void Draw(Canvas canvas) {
		
		paint.setColor(getColor());
		paint.setStyle(Paint.Style.STROKE);		
		
		canvas.drawRect(getControlRectangle(), paint );
		
	}

	@Override
	public boolean onTouchEvent(MotionEvent motion) {
		
    	touchHandler.onTouchEvent(motion);
		
		return true;
	}
	
	public boolean HitTest(int xPos, int yPos)
	{
		Rect result = getControlRectangle();
		
		if(result.contains(xPos, yPos))
			return true;
		
		return false;
	}
	
	public boolean HitTest(ScreenVector point)
	{
		return HitTest(point.x, point.y);
	}
	
	private Rect getControlRectangle()
	{
		Rect result = new Rect(getX() + padding, 
				getY() + padding, 
				getX() + getWidth() - 2*padding, 
				getY() + getHeigth() - 2*padding);
		
		return result;
	}
	
	private OnSelectHandler onSelectHandler = null;
	private int padding = 5;
	private TouchHandler touchHandler;
}

如您所见,所有触摸处理都使用我的 触摸 DSL 库实现。这些和基类是实现视图内属性编辑器的一组控件的基础。

PropertyProxyListInViewEditor

此类是直接在相机视图中编辑属性的源。它是一个 PropertyProxyInViewEditor 列表,后者是可在相机视图中编辑配置属性的一组控件的基类。

public class PropertyProxyListInViewEditor extends ItemsListCanvasWidget {

	public void setPropertyList(ArrayList<PropertyProxy> propertyList)
	{
		this.propertyList = new ArrayList<PropertyProxy>(propertyList);
		
		ArrayList<PropertyProxyInViewEditor> editorList = new ArrayList<PropertyProxyInViewEditor>();
		for(PropertyProxy property : this.propertyList)
		{
			Class<?> editorType = getEditorType(property);
			PropertyProxyInViewEditor editor = instantiatePropertyProxyInViewEditorFromClass(editorType);
			editor.setPropertyProxy(property);
			editorList.add(editor);
		}
		this.setItemsList(editorList);
	}
	
	private Class<?> getEditorType(PropertyProxy property)
	{
		InViewEditorType editorType = property.getAnnotation(InViewEditorType.class);
		if(editorType == null)
			return null;
		return editorType.Type();
	}
	
	private static PropertyProxyInViewEditor instantiatePropertyProxyInViewEditorFromClass(Class<?> itemClass)
	{
		Constructor<?> cons;
		PropertyProxyInViewEditor canvasWidget = null;
		try {
			cons = itemClass.getConstructor();
			canvasWidget = (PropertyProxyInViewEditor)cons.newInstance();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		
		return canvasWidget;
	}
	
	private ArrayList<PropertyProxy> propertyList;

}
public abstract class PropertyProxyInViewEditor extends CanvasWidgetBase {
	private PropertyProxy propertyProxy;

	public void setPropertyProxy(PropertyProxy propPrxy) {
		propertyProxy = propPrxy;
	}

	public PropertyProxy getPropertyProxy() {
		return propertyProxy;
	}

}

从这个最后一个控件派生出

  • AllowedValueSelectorInViewEditor:包含一个 ListItemSelectorCanvasWidget 控件,允许通过在控件内滑动来选择列表中的项目。
  • IntegerSliderInViewEditor:包含一个 SliderCanvasWidget 控件,允许通过滑动来设置介于最小值和最大值之间的值,使用可配置的步长。
  • IntegerUpDownInViewEditor:包含两个 ButtonCanvasWidget 控件,允许设置一个值,可选地介于最小值和最大值之间,使用可配置的步长。

作为示例,您将看到下面 IntegerUpDownInViewEditor 类的代码

public class IntegerUpDownInViewEditor extends PropertyProxyInViewEditor implements OnClickHandler {

	public IntegerUpDownInViewEditor()
	{
		leftButton.setOnClickHandler(this);
		leftButton.setColor(Color.argb(128, 0, 0, 255));
		rightButton.setOnClickHandler(this);
		rightButton.setColor(Color.argb(128, 0, 0, 255));
	}

	@Override
	public void setPropertyProxy(PropertyProxy propPrxy) {
		Class<?> validationType = propPrxy.getValidationType();
		if(validationType == IntegerRangeValidation.class)
		{
			IntegerRangeValidation validation = (IntegerRangeValidation)propPrxy.GetValidation();
			minValue = validation.MinValue();
			maxValue = validation.MaxValue();
		}
		
		IntegerStepValue stepValueAnnotation = propPrxy.getAnnotation(IntegerStepValue.class);
		if(stepValueAnnotation != null)
		{
			step = stepValueAnnotation.Step();
		}

		super.setPropertyProxy(propPrxy);
	}
	
	@Override
	public void setWidth(int width)
	{
		this.width = width;
		
		leftButton.setWidth(this.width/2);
		rightButton.setX(getX() + this.width/2);
		rightButton.setWidth(this.width/2);
	}
	
	@Override
	public void setHeight(int height)
	{
		this.height = height;
		leftButton.setHeight(this.height);
		rightButton.setHeight(this.height);
	}
	
	@Override
	public void setX(int x)
	{
		this.x = x;
		
		leftButton.setX(this.x);
		rightButton.setX(getX() + this.width/2);
	}
	
	@Override
	public void setY(int y)
	{
		this.y = y;
		
		leftButton.setY(this.y);
		rightButton.setY(this.y);
	}
	
	@Override
	public void OnClick(ButtonCanvasWidget buttonCanvasWidget) {
		int currentValue = (Integer)getPropertyProxy().getValue(int.class);
		int newValue = currentValue;
		if(buttonCanvasWidget == leftButton)
		{
			newValue = currentValue - step;
			if(newValue < minValue)
			{
				newValue = minValue;
			}
		}
		if(buttonCanvasWidget == rightButton)
		{
			newValue = currentValue + step;
			if(newValue > maxValue)
			{
				newValue = maxValue;
			}
		}
		getPropertyProxy().setValue(newValue);
	}
	
	@Override
	public void Draw(Canvas canvas) {
		
		paint.setColor(getColor());
		paint.setStyle(Paint.Style.STROKE);		
		
		leftButton.Draw(canvas);
		rightButton.Draw(canvas);
	}

	@Override
	public boolean onTouchEvent(MotionEvent motion) {
		leftButton.onTouchEvent(motion);
		rightButton.onTouchEvent(motion);
		return false;
	}

	private ButtonCanvasWidget leftButton = new ButtonCanvasWidget();
	private ButtonCanvasWidget rightButton = new ButtonCanvasWidget();
	
	private int minValue = Integer.MIN_VALUE;
	private int maxValue = Integer.MAX_VALUE;
	private int step = 1;
}

属性是否可在相机视图中编辑以及使用哪种编辑器分别由 InViewEditableInViewEditorType 注解确定

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface InViewEditable {
	boolean Editable() default true;
}
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface InViewEditorType {
	Class<?> Type();
	String ClassName() default "";
}

您可以在下面 BoxConfiguration 类的示例代码中看到它们的用法

public class BoxConfiguration extends ImageProcessingMethodConfigBase implements Parcelable, IImageProcessingMethodConfiguration   {
	
	// More code
	
	@DataTypeViewer(Type=IntegerSliderViewer.class)
	public int getKernelSize()
	{
		return kernelSize;
	}
	
	@IntegerRangeValidation(MinValue = 1, MaxValue = 15, ErrorMessage = "Value must be between 1 and 15")
	@IntegerStepValue(Step = 2)
	@InViewEditable
	@InViewEditorType(Type=IntegerUpDownInViewEditor.class)
	public void setKernelSize(int value)
	{
		kernelSize = value;
		if(callback != null)
		{
			callback.PropertyChanged(this, "ANY", null, null);
		}
	}

	private OnPropertyChangedListener callback;
	@Override
	@HideSetter
	public void setPropertyChangedListener(OnPropertyChangedListener callback) {
		this.callback = callback;
	}
	
}

视图内编辑器的设置在 AndroidVision activity 的 handleConfigMethodResultApplyConfigurationOnObjectEditor 中完成。请参阅上面的代码。

支持的图像处理方法

概述

 

如上所述,图像处理方法已分类。目前存在以下类别及其方法

  • 失去焦点
    • 盒子模糊
    • 高斯模糊
    • 中值模糊
  • 边缘检测
    • Canny 边缘检测
    • Sobel 边缘检测
  • 直方图
    • 直方图均衡化
  • 霍夫变换
    • 霍夫圆
    • 霍夫线
  • 数学形态学
    • 膨胀
    • 腐蚀

在下一节中,我将解释两种方法:SobelEdgeDetectionHoughLines。我选择这两种方法是因为第一种很简单,第二种有点复杂:因此您将看到两者的示例。但我确实邀请您下载代码并查看其他方法。

代码

SobelEdgeDetection

public class SobelEdgeDetection extends ImageProcessingMethodBase {

	public static final String METHOD_ID = "SOBEL";
	
	public SobelEdgeDetection()
	{
		configuration = new SobelConfiguration();
		configuration.setDerivXOrder(1);
		configuration.setDerivYOrder(1);
		configuration.setKernelSize(3);
		configuration.setScale(1.0);
		configuration.setDelta(0.0);
	}
	
	@Override
	public String getMethodId() {
		return METHOD_ID;
	}

	@Override
	public void setResolution(int width, int height) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void draw(Canvas canvas) {
		if(!showExtData)
		{
			return;
		}
		
		CanvasTextPage page = new CanvasTextPage(canvas);
		page.startNewPage();
		page.drawTextLine(METHOD_ID, redPaint);
		page.drawTextLine("dX order: " + configuration.getDerivXOrder(), configuration.isPropertyAvailableInView("DerivXOrder") ? greenPaint : redPaint);
		page.drawTextLine("dY order: " + configuration.getDerivYOrder(), configuration.isPropertyAvailableInView("DerivYOrder") ? greenPaint : redPaint);
		page.drawTextLine("Kernel size: " + configuration.getKernelSize(), configuration.isPropertyAvailableInView("KernelSize") ? greenPaint : redPaint);
		page.drawTextLine("Scale: " + configuration.getScale(), configuration.isPropertyAvailableInView("Scale") ? greenPaint : redPaint);
		page.drawTextLine("Delta: " + configuration.getDelta(), configuration.isPropertyAvailableInView("Delta") ? greenPaint : redPaint);

	}
	
	@Override
	public ImageType getInputImageType() {
		return ImageType.Color;
	}

	@Override
	public ImageType getOutputImageType() {
		return ImageType.Gray;
	}

	@Override
	public Mat getInputImage() {
		return inputImage;
	}

	@Override
	public void setInputImage(Mat image) {
		inputImage = image;
	}

	@Override
	public Mat getOutputImage() {
		return outputImage;
	}

	@Override
	public void setOutputImage(Mat image) {
		outputImage = image;
	}
	
	@Override
	public IImageProcessingMethodConfiguration getConfiguration()
	{
		return configuration;
	}
	
	@Override
	public void setConfiguration(IImageProcessingMethodConfiguration config)
	{
		configuration = (SobelConfiguration)config;
	}

	@Override
	public void Execute() {
		Imgproc.Sobel(inputImage, outputImage, 
				CvType.CV_8U, 
				configuration.getDerivXOrder(), 
				configuration.getDerivYOrder(), 
				configuration.getKernelSize(),
				configuration.getScale(), 
				configuration.getDelta());
	}

	SobelConfiguration configuration;
	Mat inputImage;
	Mat outputImage;
}

SobelEdgeDetectionSobelConfiguration 配置

public class SobelConfiguration extends ImageProcessingMethodConfigBase implements Parcelable, IImageProcessingMethodConfiguration {
	int derivXOrder;
	int derivYOrder;
	int kernelSize; 
	double scale;
	double delta;
	
	public SobelConfiguration()
	{
		fillInViewEditingAllowed(this);
	}
	
	public SobelConfiguration(Parcel in) 
	{ 
		fillInViewEditingAllowed(this);
		readFromParcel(in);
	}
		
	@DataTypeViewer(Type=IntegerSliderViewer.class)
	public int getDerivXOrder()
	{
		return derivXOrder;
	}
	
	@IntegerRangeValidation(MinValue = 0, MaxValue = 2, ErrorMessage = "Value must be between 0 and 2")
	@InViewEditable
	@InViewEditorType(Type=IntegerUpDownInViewEditor.class)
	public void setDerivXOrder(int threshold)
	{
		derivXOrder = threshold;
		if(callback != null)
		{
			callback.PropertyChanged(this, "ANY", null, null);
		}
	}
		
	@DataTypeViewer(Type=IntegerSliderViewer.class)
	public int getDerivYOrder()
	{
		return derivYOrder;
	}
	
	@IntegerRangeValidation(MinValue = 0, MaxValue = 2, ErrorMessage = "Value must be between 0 and 2")
	@InViewEditable
	@InViewEditorType(Type=IntegerUpDownInViewEditor.class)
	public void setDerivYOrder(int threshold)
	{
		derivYOrder = threshold;
		if(callback != null)
		{
			callback.PropertyChanged(this, "ANY", null, null);
		}
	}
	
	@DataTypeViewer(Type=IntegerSliderViewer.class)
	public int getKernelSize()
	{
		return kernelSize;
	}
	
	@IntegerRangeValidation(MinValue = 3, MaxValue = 7, ErrorMessage = "Value must be between 3 and 7")
	@IntegerStepValue(Step = 2)
	@InViewEditable
	@InViewEditorType(Type=IntegerUpDownInViewEditor.class)
	public void setKernelSize(int value)
	{
		kernelSize = value;
		if(callback != null)
		{
			callback.PropertyChanged(this, "ANY", null, null);
		}
	}
	
	public double getScale()
	{
		return scale;
	}
	
	public void setScale(double value)
	{
		scale = value;
	}
	
	public double getDelta()
	{
		return delta;
	}
	
	public void setDelta(double value)
	{
		delta = value;
	}

	private OnPropertyChangedListener callback;
	@Override
	@HideSetter
	public void setPropertyChangedListener(OnPropertyChangedListener callback) {
		this.callback = callback;
	}

	@Override
	public int describeContents() {
		return 0;
	}
	
	public void readFromParcel(Parcel in) {
		baseReadFromParcel(in);
		derivXOrder = in.readInt();
		derivYOrder = in.readInt(); 
		kernelSize = in.readInt();
		scale = in.readDouble();
		delta = in.readDouble();
	}

	@Override
	public void writeToParcel(Parcel arg0, int arg1) {
		baseWriteToParcel(arg0, arg1);
		arg0.writeInt(derivXOrder);
		arg0.writeInt(derivYOrder);
		arg0.writeInt(kernelSize);
		arg0.writeDouble(scale);
		arg0.writeDouble(delta);
	}
	
	public static final Parcelable.Creator<SobelConfiguration> CREATOR = new Parcelable.Creator<SobelConfiguration>()
	{
		@Override
		public SobelConfiguration createFromParcel(Parcel source) {
			return new SobelConfiguration(source);
		}

		@Override
		public SobelConfiguration[] newArray(int size) {
			return new SobelConfiguration[size];
		}
	};
}

HoughLines

public class HoughLines extends ImageProcessingMethodBase {

	public static final String METHOD_ID = "HOUGHLINES";
	
	public HoughLines()
	{
		configuration = new HoughLinesConfiguration();
		configuration.setLowerThreshold(125);
		configuration.setUpperThreshold(350);
		configuration.setApertureSize(3);
		configuration.setL2Gradient(false);
		
		configuration.setRho(1);
		configuration.setTheta(1);
		configuration.setThreshold(80);
		
		CopyConfiguration();
	}
	
	@Override
	public String getMethodId() {
		return METHOD_ID;
	}

	@Override
	public void setResolution(int width, int height) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void draw(Canvas canvas) {
		if(!showExtData)
		{
			return;
		}
		
		CanvasTextPage page = new CanvasTextPage(canvas);
		page.startNewPage();
		page.drawTextLine(METHOD_ID, redPaint);

		page.drawTextLine("Lower threshold: " + lowerThreshold, configuration.isPropertyAvailableInView("LowerThreshold") ? greenPaint : redPaint);
		page.drawTextLine("Upper threshold: " + upperThreshold, configuration.isPropertyAvailableInView("UpperThreshold") ? greenPaint : redPaint);
		page.drawTextLine("Aperture size: " + apertureSize, configuration.isPropertyAvailableInView("ApertureSize") ? greenPaint : redPaint);
		page.drawTextLine("L2 gradient: " + L2Gradient, configuration.isPropertyAvailableInView("L2Gradient") ? greenPaint : redPaint);

		page.drawTextLine("Rho: " + rho, configuration.isPropertyAvailableInView("Rho") ? greenPaint : redPaint);
		page.drawTextLine("Theta: " + theta, configuration.isPropertyAvailableInView("Theta") ? greenPaint : redPaint);
		page.drawTextLine("Threshold: " + threshold, configuration.isPropertyAvailableInView("Threshold") ? greenPaint : redPaint);
	}
	
	@Override
	public ImageType getInputImageType() {
		return ImageType.Color;
	}

	@Override
	public ImageType getOutputImageType() {
		return ImageType.Gray;
	}

	@Override
	public Mat getInputImage() {
		return inputImage;
	}

	@Override
	public void setInputImage(Mat image) {
		inputImage = image;
	}

	@Override
	public Mat getOutputImage() {
		return outputImage;
	}

	@Override
	public void setOutputImage(Mat image) {
		outputImage = image;
	}
	
	@Override
	public IImageProcessingMethodConfiguration getConfiguration()
	{
		return configuration;
	}
	
	@Override
	public void setConfiguration(IImageProcessingMethodConfiguration config)
	{
		configuration = (HoughLinesConfiguration)config;
		
		CopyConfiguration();
	}

	@Override
	public void Execute() {

		double thetaInRadians = theta * 180 / Math.PI;
		
		Mat cannyMat = new Mat();
    	Mat lineMat = new Mat();
		Imgproc.Canny(inputImage, outputImage, lowerThreshold, upperThreshold, apertureSize, L2Gradient);
		Imgproc.Canny(inputImage, cannyMat, lowerThreshold, upperThreshold, apertureSize, L2Gradient);
		Imgproc.HoughLines(cannyMat, lineMat, rho, thetaInRadians, threshold);
		
		DrawHoughLines(lineMat);
	}
	
	private void DrawHoughLines(Mat lineMat)
	{
		Scalar color = new Scalar(255, 255, 255); 
		
		double[] data; 
		double rho, theta; 
		Point pt1 = new Point(); 
		Point pt2 = new Point(); 
		double a, b; 
		double x0, y0; 
		for (int i = 0; i < lineMat.cols(); i++) {     
			data = lineMat.get(0, i);
			rho = data[0];     
			theta = data[1];     
			a = Math.cos(theta);     
			b = Math.sin(theta);     
			x0 = a*rho;     
			y0 = b*rho;     
			pt1.x = Math.round(x0 + 1000*(-b));     
			pt1.y = Math.round(y0 + 1000*a);     
			pt2.x = Math.round(x0 - 1000*(-b));     
			pt2.y = Math.round(y0 - 1000 *a);     
			Core.line(outputImage, pt1, pt2, color, 3); 
		} 		
	}
	
	private void CopyConfiguration()
	{
		lowerThreshold = configuration.getLowerThreshold(); 
		upperThreshold = configuration.getUpperThreshold(); 
		apertureSize = configuration.getApertureSize();
		L2Gradient = configuration.getL2Gradient();
		
		rho = configuration.getRho();
		theta = configuration.getTheta();
		threshold = configuration.getThreshold();
	}

	HoughLinesConfiguration configuration;
	Mat inputImage;
	Mat outputImage;
	
	int lowerThreshold;
	int upperThreshold;
	int apertureSize;
	boolean L2Gradient;
	
	double rho;
	double theta;
	int threshold;

}

HoughLinesHoughLinesConfiguration 配置

public class HoughLinesConfiguration extends ImageProcessingMethodConfigBase implements Parcelable, IImageProcessingMethodConfiguration {
	int lowerThreshold; 
	int upperThreshold; 
	int apertureSize;
	boolean L2Gradient;
	
	int rho;
	int theta;
	int threshold;
	
	public HoughLinesConfiguration()
	{
	
	}
	
	public HoughLinesConfiguration(Parcel in) 
	{ 
		lowerThreshold = in.readInt();
		upperThreshold = in.readInt(); 
		apertureSize = in.readInt();
		L2Gradient = in.readInt()==1?true:false;
		
		rho = in.readInt();
		theta = in.readInt();
		threshold = in.readInt();
	}
		
	@Category(Name="Canny")
	public int getLowerThreshold()
	{
		return lowerThreshold;
	}
	
	@InViewEditable
	@InViewEditorType(Type=IntegerSliderInViewEditor.class)
	@IntegerRangeValidation(MinValue = 0, MaxValue = 200, ErrorMessage = "Value must be between 0 and 200")
	public void setLowerThreshold(int threshold)
	{
		lowerThreshold = threshold;
		if(callback != null)
		{
			callback.PropertyChanged(this, "ANY", null, null);
		}
	}
	
	@Category(Name="Canny")
	public int getUpperThreshold()
	{
		return upperThreshold;
	}
	
	@InViewEditable
	@InViewEditorType(Type=IntegerSliderInViewEditor.class)
	@IntegerRangeValidation(MinValue = 0, MaxValue = 200, ErrorMessage = "Value must be between 0 and 200")
	public void setUpperThreshold(int threshold)
	{
		upperThreshold = threshold;
		if(callback != null)
		{
			callback.PropertyChanged(this, "ANY", null, null);
		}
	}

	@Category(Name="Canny")
	public int getApertureSize() {
		return apertureSize;
	}

	@InViewEditable
	@InViewEditorType(Type=IntegerUpDownInViewEditor.class)
	public void setApertureSize(int value) {
		apertureSize = value;
		if(callback != null)
		{
			callback.PropertyChanged(this, "ANY", null, null);
		}
	}

	@Category(Name="Canny")
	public boolean getL2Gradient() {
		return L2Gradient;
	}

	public void setL2Gradient(boolean value) {
		L2Gradient = value;
		if(callback != null)
		{
			callback.PropertyChanged(this, "ANY", null, null);
		}
	}

	@Category(Name="Hough transform")	
	public int getRho() {
		return rho;
	}

	public void setRho(int value) {
		rho = value;
		if(callback != null)
		{
			callback.PropertyChanged(this, "ANY", null, null);
		}
	}

	@Category(Name="Hough transform")	
	public int getTheta() {
		return theta;
	}

	@IntegerRangeValidation(MinValue = 1, MaxValue = 360, ErrorMessage = "Value must be between 1 and 360")
	@InViewEditable
	@InViewEditorType(Type=IntegerUpDownInViewEditor.class)
	public void setTheta(int value) {
		theta = value;
		if(callback != null)
		{
			callback.PropertyChanged(this, "ANY", null, null);
		}
	}

	@Category(Name="Hough transform")	
	public int getThreshold() {
		return threshold;
	}

	@InViewEditable
	@InViewEditorType(Type=IntegerUpDownInViewEditor.class)
	public void setThreshold(int value) {
		threshold = value;
		if(callback != null)
		{
			callback.PropertyChanged(this, "ANY", null, null);
		}
	}

	private OnPropertyChangedListener callback;
	@Override
	@HideSetter
	public void setPropertyChangedListener(OnPropertyChangedListener callback) {
		this.callback = callback;
	}

	@Override
	public int describeContents() {
		return 0;
	}

	@Override
	public void writeToParcel(Parcel arg0, int arg1) {
		arg0.writeInt(lowerThreshold);
		arg0.writeInt(upperThreshold);
		arg0.writeInt(apertureSize);
		arg0.writeInt(L2Gradient?1:0);
		
		arg0.writeInt(rho);
		arg0.writeInt(theta);
		arg0.writeInt(threshold);
	}
	
	public static final Parcelable.Creator<HoughLinesConfiguration> CREATOR = new Parcelable.Creator<HoughLinesConfiguration>()
	{
		@Override
		public HoughLinesConfiguration createFromParcel(Parcel source) {
			return new HoughLinesConfiguration(source);
		}

		@Override
		public HoughLinesConfiguration[] newArray(int size) {
			return new HoughLinesConfiguration[size];
		}
	};
}

结论

好了,本次发布就到这里。希望您喜欢这篇文章。如前所述,这是“立即发布”版本,因此还有(很多)改进空间,例如更多方法、更漂亮的界面等等。因此……

下一步是什么?

我的愿望清单上还有一些功能

  • 实现更多方法
  • 工作流功能
  • 多线程支持
  • 实现其他图像源,如前置摄像头、图像画廊
  • 美化界面(动画、更漂亮的控件等)

版本历史

  • 版本 1.0:初始版本
  • 版本 1.1:代码清理
© . All rights reserved.