图像中的信息包括边缘、直线、椭圆、色块或轮廓、角点等形式,这些信息在计算机视觉和图像处理语境中通常被称为特征。下面就来了解一些结合OpenCV在Android平台上的常规的特征检测算法,这里使用AndroidStudio开发平台,当然Eclipse也一样。如果对在这两个IDE上部署OpenCV不熟悉的话,可以参考我之前写的这篇文章:Eclipse与AndroidStudio关于OpenCV4Android库的部署
1.边缘和焦点检测
边缘检测和角点检测时两种最基本也是非常有用的特征检测算法,这些算法包括高斯差分、Canny边缘检测器、Sobel算子和Harris角点。比如我们想在一幅图中找到不同目标的边界或者角点,用于分析目标在图像中的旋转或移动等情况,这时候就需要知道边缘和角点的信息了。
-
高斯差分
这里我们先了解下什么是边缘和高斯模糊方法,因为下面要用到边缘这个性质和对图像进行高斯模糊来计算边缘点,即边缘点。- 简单的说,边缘就是图像中像素亮度变化明显的点。
- 高斯(Gaussian)模糊是最常用的模糊方法,是将指定像素变换为其与周边像素加权平均后的值,权重就是高斯分布函数计算出来的值。OpenCV提供了GaussianBlur()的内置函数,我们可以在应用中使用它执行高斯模糊。这里是一篇关于高斯模糊算法的介绍:高斯模糊算法
形如:
Mat src;
Imgproc.GaussianBlur(src,src,new Size(6,6),0);让我们回到高斯差分技术,其算法分成三步:
- 将图像转换为弧度图像
- 用两个不同的模糊半径对灰色图像执行高斯模糊
- 对上面两幅图像相减,得到一副只包含边缘点的结果图像
在应用中,创建一个用于给定图像计算边缘的函数:
/**
* 高斯差分
*/
private void DifferenceOfGaussian() {
Mat grayMat = new Mat();
Mat blur1 = new Mat();
Mat blur2 = new Mat();
//将图像转换为灰度
Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_BGR2GRAY);
//以两个不同的模糊半径对图像做模糊处理,前两个参数分别是输入和输出图像,第三个参数指定应用滤波器时所用核的尺寸,最后一个参数指定高斯函数中的标准差数值
Imgproc.GaussianBlur(grayMat, blur1, new Size(15, 15), 5);
Imgproc.GaussianBlur(grayMat, blur2, new Size(21, 21), 5);
//将两幅模糊后的图像相减
Mat DoG = new Mat();
Core.absdiff(blur1, blur2, DoG);
//反转二值阈值化
Core.multiply(DoG, new Scalar(100), DoG);
Imgproc.threshold(DoG, DoG, 50, 255, Imgproc.THRESH_BINARY_INV);
//将Mat转换为位图
Utils.matToBitmap(DoG, currentBitmap);
loadImageToImageView();
}
/**
* 设置图像
*/
private void loadImageToImageView() {
ImageView imgView = (ImageView) findViewById(R.id.image_view);
imgView.setImageBitmap(currentBitmap);
}
处理结果图:
- Canny边缘检测器
Canny边缘检测器算法使用了多向灰度梯度和滞后阈值化等复杂技巧,它是边缘检测的最优方法。
算法分为四个步骤:- 平滑图:使用合适的模糊半径执行高斯模糊来减少图像内的噪声
- 计算图像梯度:计算图像梯度,并将其分为垂直、水平和斜对角
- 非最大值:由图像梯度计算得到梯度方向,检查某一像素在梯度正方向和负方向的局部最大值,然后抑制该像素
- 用滞后阈值化选择边缘:检查某一条边缘是否明显到足以作为最终输出,最后取出所有不够明显的边缘
下面是OpenCV对该算法在Android上的实现代码:
/**
* Canny边缘检测器
*/
private void Canny() {
Mat grayMat = new Mat();
Mat cannyEdges = new Mat();
//将图像转换为灰度
Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_RGB2GRAY);
Imgproc.Canny(grayMat, cannyEdges, 10, 100);
//将Mat转换回位图
Utils.matToBitmap(cannyEdges, currentBitmap);
loadImageToImageView();
}
/**
* 设置图像
*/
private void loadImageToImageView() {
ImageView imgView = (ImageView) findViewById(R.id.image_view);
imgView.setImageBitmap(currentBitmap);
}
处理结果图:
- Sobel算子
Sobel算子和Canny边缘检测一样,计算像素灰度梯度。
算法分为四个步骤:- 将图像转换为灰度图像
- 计算水平方向灰度梯度绝对值
- 计算垂直方向灰度梯度绝对值
- 计算最终梯度
下面是OpenCV对该算法在Android上的实现代码:
/**
* Sobel算子
*/
private void Sobel() {
Mat grayMat = new Mat();
//用来保存结果的Mat
Mat sobel = new Mat();
//分别用于保存梯度和绝对梯度的Mat
Mat grad_x = new Mat();
Mat abs_grad_x = new Mat();
Mat grad_y = new Mat();
Mat abs_grad_y = new Mat();
//将图像转换为灰度
Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_BGR2GRAY);
//计算水平方向梯度
Imgproc.Sobel(grayMat, grad_x, CvType.CV_16S, 1, 0, 3, 1, 0);
//计算垂直方向梯度
Imgproc.Sobel(grayMat, grad_y, CvType.CV_16S, 0, 1, 3, 1, 0);
//计算两个方向上梯度绝对值
Core.convertScaleAbs(grad_x, abs_grad_x);
Core.convertScaleAbs(grad_y, abs_grad_y);
//计算结果梯度
Core.addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 1, sobel);
//将Mat转换为位图
Utils.matToBitmap(sobel, currentBitmap);
loadImageToImageView();
}
/**
* 设置图像
*/
private void loadImageToImageView() {
ImageView imgView = (ImageView) findViewById(R.id.image_view);
imgView.setImageBitmap(currentBitmap);
}
处理结果图:
- Harris角点检测
Harris焦点检测器是一种在角点检测中最常用的技术,在图像上使用滑动窗口计算亮度的变化。
下面是OpenCV对该算法在Android上的实现代码:
/**
* Harris角点
*/
private void HarrisCorner() {
Mat grayMat = new Mat();
Mat corners = new Mat();
//将图像转换成灰度
Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_BGR2GRAY);
Mat tempDst = new Mat();
//找出角点
Imgproc.cornerHarris(grayMat, tempDst, 2, 3, 0.04);
//在新的图像上绘制角点
Mat tempDstNorm = new Mat();
Core.normalize(tempDst, tempDstNorm, 0, 255, Core.NORM_MINMAX);
Core.convertScaleAbs(tempDstNorm, corners);
//将Mat转换为位图
Random r = new Random();
for (int i = 0; i < tempDstNorm.cols(); i++) {
for (int j = 0; j < tempDstNorm.rows(); j++) {
double[] value = tempDstNorm.get(j, i);
if (value[0] > 150)
Core.circle(corners, new Point(i, j), 5, new Scalar(r.nextInt(255)), 2);
}
}
//将Mat转换为位图
Utils.matToBitmap(corners, currentBitmap);
loadImageToImageView();
}
/**
* 设置图像
*/
private void loadImageToImageView() {
ImageView imgView = (ImageView) findViewById(R.id.image_view);
imgView.setImageBitmap(currentBitmap);
}
处理结果图:
2.霍夫变换
对于图像分析来说,除了边缘和角点外,还需要检测如直线、圆、椭圆等其他相关形状。例如我们想在一张桌子的图像中检测签字笔等,对于这种情况,一般采用霍夫变换这种技术,它被广泛采用的利用数学等式的参数形式在图像中检测形状的技术。通常我们考虑二维形状的霍夫变换,比如直线和圆,它相对于球体等复杂形状而言更为简单。
-
霍夫直线
我们对于霍夫变化一般不会直接对图像执行算法,因为图像中任何一条明显的直线都一定是边缘,反之则不然。OpenCV提供了两种实现霍夫直线的方式:标准霍夫直线和概率霍夫直线。它们的主要区别在于概率霍夫直线选取经过随机采样的边缘点子集,而不是所有的边缘点。因为这种方式处理的点更少,所以这使得算法的执行速度更快,而不会降低算法的效果。
下面是OpenCV对该算法在Android上的实现代码:/**
* 霍夫直线
*/
private void HoughLines() {
Mat grayMat = new Mat();
Mat cannyEdges = new Mat();
Mat lines = new Mat();
//将图像转换成灰度
Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_BGR2GRAY);
//这里使用Canny边缘检测技术计算图像中的边缘,使用其他的也可以
Imgproc.Canny(grayMat, cannyEdges, 10, 100);
Imgproc.HoughLinesP(cannyEdges, lines, 1, Math.PI / 180, 50, 20, 20);
Mat houghLines = new Mat();
houghLines.create(cannyEdges.rows(), cannyEdges.cols(), CvType.CV_8UC1);
//在图像上绘制直线
for (int i = 0; i < lines.cols(); i++) {
double[] points = lines.get(0, i);
double x1, y1, x2, y2;
x1 = points[0];
y1 = points[1];
x2 = points[2];
y2 = points[3];
Point pt1 = new Point(x1, y1);
Point pt2 = new Point(x2, y2);
//在一副图像上绘制直线
Core.line(houghLines, pt1, pt2, new Scalar(255, 0, 0), 1);
}
//将Mat转换为位图
Utils.matToBitmap(houghLines, currentBitmap);
loadImageToImageView();
}
/**
* 设置图像
*/
private void loadImageToImageView() {
ImageView imgView = (ImageView) findViewById(R.id.image_view);
imgView.setImageBitmap(currentBitmap);
}处理结果图:
-
霍夫圆
与霍夫直线类似,步骤也一样。
下面是OpenCV对该算法在Android上的实现代码:
/**
*霍夫圆
*/
private void HoughCircles() {
Mat grayMat = new Mat();
Mat cannyEdges = new Mat();
Mat circles = new Mat();
//将图像转换成灰度
Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_BGR2GRAY);
//这里使用Canny边缘检测技术计算图像中的边缘,使用其他的也可以
Imgproc.Canny(grayMat, cannyEdges, 10, 100);
Imgproc.HoughCircles(cannyEdges, circles, Imgproc.CV_HOUGH_GRADIENT, 1, cannyEdges.rows() / 15);
Mat houghCircles = new Mat();
houghCircles.create(cannyEdges.rows(), cannyEdges.cols(), CvType.CV_8UC1);
//在图像上绘制圆形
for (int i = 0; i < circles.cols(); i++) {
double[] parameters = circles.get(0, i);
double x, y;
int r;
x = parameters[0];
y = parameters[1];
r = (int) parameters[2];
Point center = new Point(x, y);
//在一副图像上绘制圆形
Core.circle(houghCircles, center, r, new Scalar(255, 0, 0), 1);
}
//将Mat转换为位图
Utils.matToBitmap(houghCircles, currentBitmap);
loadImageToImageView();
}
/**
* 设置图像
*/
private void loadImageToImageView() {
ImageView imgView = (ImageView) findViewById(R.id.image_view);
imgView.setImageBitmap(currentBitmap);
}
处理结果图:
3.轮廓
有时候我们只想关注于感兴趣的目标,这就需要将图像分解成更小的片元,比如我们在一角、五角和一元硬币中分析五角的情况。我们有两种实现途径,一种是使用霍夫圆,另一种就是利用轮廓检测将图像分割为更小的部分,每个部分代表一个特定的硬币。轮廓通常以图像中的边缘来计算,边缘与轮廓的区别在于轮廓是闭合的,边缘可以是任意的。
下面是OpenCV对该算法在Android上的实现代码:
/**
* 轮廓检测
*/
private void Contours() {
Mat grayMat = new Mat();
Mat cannyEdges = new Mat();
Mat hierarchy = new Mat();
List<MatOfPoint> contourList = new ArrayList<MatOfPoint>(); //A list to store all the contours
//保存轮廓列表
Imgproc.cvtColor(originalMat, grayMat, Imgproc.COLOR_BGR2GRAY);
//这里使用Canny边缘检测技术计算图像中的边缘,使用其他的也可以
Imgproc.Canny(originalMat, cannyEdges, 10, 100);
//找出轮廓
Imgproc.findContours(cannyEdges, contourList, hierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
//在新的图像上绘制轮廓
Mat contours = new Mat();
contours.create(cannyEdges.rows(), cannyEdges.cols(), CvType.CV_8UC3);
Random r = new Random();
for (int i = 0; i < contourList.size(); i++) {
Imgproc.drawContours(contours, contourList, i, new Scalar(r.nextInt(255), r.nextInt(255), r.nextInt(255)), -1);
}
//将Mat转换为位图
Utils.matToBitmap(contours, currentBitmap);
loadImageToImageView();
}
/**
* 设置图像
*/
private void loadImageToImageView() {
ImageView imgView = (ImageView) findViewById(R.id.image_view);
imgView.setImageBitmap(currentBitmap);
}
处理结果图:
以上就是图像的基本特征在Android设备上实现的不同算法,也是我们在Android相关应用开发中的基础。