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






4.95/5 (20投票s)
尝试在您的手机上使用 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 的一项更改是它现在实现了三个菜单选项
- 选择:允许您选择图像处理方法。
- 配置:允许您配置所选方法。
- 显示:在屏幕上显示方法的配置。
代码
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();
}
}
注册 FramegrabberView
的 FrameGrabberViewBase.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
中绘制内容的有两个对象
ImageProcessingMethod
可以绘制其配置设置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>();
}
最后,选定的 IImageProcessingMethod
在 onCameraFrame
回调方法中应用:(请参阅 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;
}
属性是否可在相机视图中编辑以及使用哪种编辑器分别由 InViewEditable
和 InViewEditorType
注解确定
@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 的 handleConfigMethodResult
和 ApplyConfigurationOnObjectEditor
中完成。请参阅上面的代码。
支持的图像处理方法
概述
如上所述,图像处理方法已分类。目前存在以下类别及其方法
- 失去焦点
- 盒子模糊
- 高斯模糊
- 中值模糊
- 边缘检测
- Canny 边缘检测
- Sobel 边缘检测
- 直方图
- 直方图均衡化
- 霍夫变换
- 霍夫圆
- 霍夫线
- 数学形态学
- 膨胀
- 腐蚀
在下一节中,我将解释两种方法:SobelEdgeDetection
和 HoughLines
。我选择这两种方法是因为第一种很简单,第二种有点复杂:因此您将看到两者的示例。但我确实邀请您下载代码并查看其他方法。
代码
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;
}
SobelEdgeDetection
由 SobelConfiguration
配置
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;
}
HoughLines
由 HoughLinesConfiguration
配置
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:代码清理