opencv4android常用变换(三)
这是opencv4android的第三篇,从这里开始就简单的写,要不我没有时间去学别的了!大家加油。
Laplace 算子
检测边缘的一种算法,是一个二阶导数就是Sobel的导数,可以这么理解,是边界轮廓更加清晰明显。
方法: Laplacian(grayMat,dstMat,-1,3,1,0,Core.BORDER_DEFAULT);
参数说明:原图像,目标图像,深度,内核大小,后三个为默认值。
代码:
Mat grayMat=new Mat();
Utils.bitmapToMat(mBitmap,rgbMat);
Imgproc.GaussianBlur(rgbMat,grayMat,new Size(3,3),0,0,Core.BORDER_DEFAULT);
Imgproc.cvtColor(grayMat,grayMat,Imgproc.COLOR_BGR2GRAY);
Imgproc.Laplacian(grayMat,dstMat,-1,3,1,0,Core.BORDER_DEFAULT);
Utils.matToBitmap(dstMat,dstBitmap);
dstImage.setImageBitmap(dstBitmap);
效果:
从效果图上来看,确实要比Sobel的边缘检测效果要好很多,能够很明显的看出帽子上羽毛的轮廓。
Canny 边缘检测
Canny 边缘检测算法 是 John F. Canny 于 1986年开发出来的一个多级边缘检测算法,也被很多人认为是边缘检测的 最优算法, 最优边缘检测的三个主要评价标准是:
- 低错误率: 标识出尽可能多的实际边缘,同时尽可能的减少噪声产生的误报。
- 高定位性: 标识出的边缘要与图像中的实际边缘尽可能接近。
- 最小响应: 图像中的边缘只能标识一次。
—引自opencv教程。
方法:Imgproc.Canny(grayMat,dstMat,n,3*n,3,true);
参数:灰度图像,目标图像,最低阈值,最高阈值,内核大小,不知道。
代码:
Mat grayMat=new Mat();
Utils.bitmapToMat(mBitmap,rgbMat);
Imgproc.cvtColor(rgbMat,grayMat,Imgproc.COLOR_BGR2GRAY);
Imgproc.GaussianBlur(grayMat,grayMat,new Size(3,3),0,0,Core.BORDER_DEFAULT);
Imgproc.Canny(grayMat,dstMat,n,3*n,3,true);
Utils.matToBitmap(dstMat,dstBitmap);
dstImage.setImageBitmap(dstBitmap);
解释一下,先灰度之后高斯平滑,之后在边缘检测。
效果图:
在解释一下,进度条最大值代表最低阈值,也就是第三个参数,第四个参数是第三个的2-3倍最好,我使用3倍,内核是3,opencv教程中使用的归一块平滑,我用的是高斯,可能效果上有所差异,不过轮廓的清晰度还是很好的。
霍夫线变换
霍夫线变换是一种用来寻找直线的方法.
是用霍夫线变换之前, 首先要对图像进行边缘检测的处理,也即霍夫线变换的直接输入只能是边缘二值图像.
在写这个的时候已经距离我写之前的两个有一个礼拜的时间了。这个霍夫线变化实在是有点费劲,主要是在容器的创建和使用上,完全搞不懂,如果有会的希望能给我留言,交流一下, QQ755457743.标准的霍夫线变换没写,因为那个容器没法弄,搞了好几天都没搞好,都准备弃了,统计概率霍夫线变换写出来了,不过感觉不对啊,跟opencv教程上的差好多,不过也算是弄出来了,就发一下看看。
方法: HoughLinesP(Mat image, Mat lines, double rho, double theta, int threshold, double minLineLength, double maxLineGap)
参数:
- image: 边缘检测的输出图像. 它应该是个灰度图 (但事实上是个二值化图)
- lines: 储存着检测到的直线的参数对 (x_{start}, y_{start}, x_{end}, y_{end}) 的容器
- rho : 参数极径 r 以像素值为单位的分辨率. 我们使用 1 像素.
- theta: 参数极角 \theta 以弧度为单位的分辨率. 我们使用 1度 (即CV_PI/180)
- threshold: 要”检测” 一条直线所需最少的的曲线交点
- minLinLength: 能组成一条直线的最少点的数量. 点数量不足的直线将被抛弃.
- maxLineGap: 能被认为在一条直线上的亮点的最大距离.
其中最费劲的就是这个lines容器,Mat对象。完全不知道怎么写。各种尝试。最后在stackoverflow看到一个人的回答,参照他的写法才勉强弄出图像的。
代码:
Mat srcMat=new Mat();
Mat dstMat=new Mat();
Mat thresholdImage=new Mat();
Utils.bitmapToMat(mBitmap,srcMat);
Imgproc.cvtColor(srcMat,thresholdImage,Imgproc.COLOR_BGR2GRAY,4);
Imgproc.cvtColor(srcMat,dstMat,Imgproc.COLOR_BGR2RGB,4);
Imgproc.Canny(thresholdImage,thresholdImage,50,200,3,true);
Mat lines=new Mat();
int threshold=50,minLineSize=50,lineGap=10;
Imgproc.HoughLinesP(thresholdImage,lines,1,1,n,n,lineGap);
// Imgproc.HoughLines(thresholdImage,lines,1,1,n,0,0);
for(int i=0;i<lines.cols();i++){
for(int j=0;j<lines.rows();j++){
double[] vec=lines.get(j,i);
double x1 = vec[0],
y1 = vec[1],
x2 = vec[2],
y2 = vec[3];
Point start = new Point(x1, y1);
Point end = new Point(x2, y2);
Log.d("TAG","Point: "+start.toString()+"/"+end.toString());
Imgproc.line(dstMat,start,end,new Scalar(255,0,0),3);
Log.d("TAG","arrays : +" +Arrays.toString(vec));
}
}
Log.d("TAG","dst: "+dstMat.size()+"/"+dstMat.type()+"/"+dstMat.rows()+"/"+dstMat.cols());
Utils.matToBitmap(dstMat,dstBitmap);
dstImage.setImageBitmap(dstBitmap);
效果图:
可以看出来,只是有线的效果,而且只有在阈值特别低的情况下才能明显的感觉到有窗户,感觉是我写的不对。在上面的代码中,主要的坑就是在第二个参数坐标的容器,卡了好久,完全没有思路,现在的这种方式也是借鉴来的,自己加了一些改动,可能是自己对于矩阵这个容器的理解不是很深刻,上面你的代码我也不是很理解,矩阵搞得每一个元素都存储了两个点的坐标,也就是一条直线的起点和终点,我不知道对不对。
霍夫圆变换
跟霍夫线变换类似用于检测圆的,这个写的没问题,不过需要根据阈值拉调整。
方法:
HoughCircles(Mat image, Mat circles, int method, double dp, double minDist, double param1, double param2, int minRadius, int maxRadius)
参数:
- image: 输入图像 (灰度图)
- circles: 存储下面三个参数: x_{c}, y_{c}, r 集合的容器来表示每个检测到的圆.
- CV_HOUGH_GRADIENT: 指定检测方法. 现在OpenCV中只有霍夫梯度法
- dp = 1: 累加器图像的反比分辨率
- min_dist = src_gray.rows/8: 检测到圆心之间的最小距离
- param_1 = 200: Canny边缘函数的高阈值
- param_2 = 100: 圆心检测阈值.
- min_radius = 0: 能检测到的最小圆半径, 默认为0.
- max_radius = 0: 能检测到的最大圆半径, 默认为0
代码:
Mat src=new Mat();
Mat dst=new Mat();
Mat gray=new Mat();
Utils.bitmapToMat(mBitmap,src);
Imgproc.cvtColor(src,gray,Imgproc.COLOR_BGR2GRAY);
Imgproc.GaussianBlur(gray,gray,new Size(9,9),2,2);
Mat circles=new Mat();
Imgproc.HoughCircles(gray,circles,Imgproc.CV_HOUGH_GRADIENT,1,gray.rows()/8,4*n,100,0,0);
for(int i=0;i<circles.cols();i++){
for(int j =0;j<circles.rows();j++){
double[] c=circles.get(j,i);
Log.d("TAG","c: "+ Arrays.toString(c));
Point center=new Point(c[0],c[1]);
Imgproc.circle(src,center, (int) c[2],new Scalar(255,0,0),3,8,0);
Imgproc.circle(src,center, 3,new Scalar(0,255,0),-1,8,0);
}
}
Log.d("TAG","circle: "+circles.size()+"/"+circles.cols()+"/"+circles.rows());
Utils.matToBitmap(src,dstBitmap);
dstImage.setImageBitmap(dstBitmap);
这个代码是没有问题的,效果也是可以的,如果没有达到预期可能是因为param_1的参数问题。
效果图:
可以看到不同的值对应这不同的圆。
Remapping 重映射
把一个图像中一个位置的像素放置到另一个图片指定位置的过程.这个效果没有实现,只实现了像素的挪动没有达到opencv中的效果。
方法:
remap(Mat src, Mat dst, Mat map1, Mat map2, int interpolation, int borderMode, Scalar borderValue)
参数:
- src: 源图像
- dst: 目标图像,与 src 相同大小
- map_x: x方向的映射参数. 它相当于方法 h(i,j) 的第一个参数
- map_y: y方向的映射参数. 注意 map_y 和 map_x 与 src 的大小一致。
- CV_INTER_LINEAR: 非整数像素坐标插值标志. 这里给出的是默认值(双线性插值).
- BORDER_CONSTANT: 默认
代码:
if(rgbMat==null|dstMat==null){
rgbMat=new Mat();
dstMat=new Mat();
}
Utils.bitmapToMat(mBitmap,rgbMat);
Mat src_x=new Mat(rgbMat.size(),CvType.CV_32FC1);
Mat src_y=new Mat(rgbMat.size(),CvType.CV_32FC1);
for(int j=1;j<rgbMat.rows();j++){
for (int i=1;i<rgbMat.cols();i++){
double[] x=rgbMat.get(j,rgbMat.cols()-i);
double[] y=rgbMat.get(rgbMat.rows()-j,i);
double[] s=rgbMat.get(rgbMat.rows()-j,rgbMat.cols()-i);
double[] o=rgbMat.get(j,i);
src_x.put(j,i,s);
}
}
Imgproc.remap(rgbMat,dstMat,src_x,src_y,Imgproc.INTER_LINEAR,Core.BORDER_CONSTANT,new Scalar(0,0,0));
Utils.matToBitmap(dstMat,dstBitmap);
dstImage.setImageBitmap(dstBitmap);
效果图:
参照opencv教程中代码,经过自己的理解和改写之后在opencv4android中实现效果就这样,能够看出来确实像素点的位置对调了,但是像素值却发生了改变,不清楚为什么会发生这种情况,个人猜测是因为src_y和src_x的原因,我也尝试了不用的位置偏差,和混合方式,结果都不行,像素值在转换过来之后都发生了改变。至此无果。
结束语
opencv4android到现在已经写了4篇了,从环境搭建道基本使用,说实话我都不知道这个东西是干嘛的,就只是朋友要就学了一点,之后自己感兴趣就多学了一些,不过到这里也就结束了,这一篇是暂时的完结,也不知道以后会不会写, 因为这个东西对于现在的我来说实在是费劲,从图像的知识到C再到opencv,心力交瘁,有时候一个很简单的东西都会卡很久,兴趣也变成无趣了,为自己之前的狂妄自大说个对不起,就到这里了,写不下去。