Android平台OpenCV透视变换及矩形检测优化问题咨询
嘿,作为OpenCV Android新手能成功检测出矩形已经迈出了很棒的一步!我来帮你搞定透视变换的实现,同时给你一些矩形检测的优化建议~
问题背景
我是Android平台OpenCV新手,已成功检测出图像中的矩形,但不清楚如何使用getPerspectiveTransform()和warpPerspective()函数完成透视变换(含旋转操作);同时希望获取矩形检测的优化建议。
以下是我的矩形检测函数:
Mat tempMat = new Mat(); Mat src = new Mat(); Utils.bitmapToMat(image, tempMat); Imgproc.cvtColor(tempMat, src, Imgproc.COLOR_BGR2RGB); Mat blurred = src.clone(); Imgproc.medianBlur(src, blurred, 9); Mat gray0 = new Mat(blurred.size(), CvType.CV_8U), gray = new Mat(); List<MatOfPoint> contours = new ArrayList<MatOfPoint>(); List<Mat> blurredChannel = new ArrayList<Mat>(); blurredChannel.add(blurred); List<Mat> gray0Channel = new ArrayList<Mat>(); gray0Channel.add(gray0); MatOfPoint2f approxCurve = new MatOfPoint2f(); double maxArea = 0; int maxId = -1; for (int c = 0; c < 3; c++) { int ch[] = { c, 0 }; Core.mixChannels(blurredChannel, gray0Channel, new MatOfInt(ch)); int thresholdLevel = 1; for (int t = 0; t < thresholdLevel; t++) { if (t == 0) { Imgproc.Canny(gray0, gray, 50, 50, 3, true); // true ? Imgproc.dilate(gray, gray, new Mat(), new Point(-1, -1), 1); // 1 ? } else { Imgproc.adaptiveThreshold(gray0, gray, thresholdLevel, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, Imgproc.THRESH_BINARY, (src.width() + src.height()) / 200, t); } Imgproc.findContours(gray, contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE); for (MatOfPoint contour : contours) { MatOfPoint2f temp = new MatOfPoint2f(contour.toArray()); double area = Imgproc.contourArea(contour); approxCurve = new MatOfPoint2f(); Imgproc.approxPolyDP(temp, approxCurve, Imgproc.arcLength(temp, true) * 0.02, true); if (approxCurve.total() == 4 && area >= maxArea) { double maxCosine = 0; List<Point> curves = approxCurve.toList(); for (int j = 2; j < 5; j++) { double cosine = Math.abs(angle(curves.get(j % 4), curves.get(j - 2), curves.get(j - 1))); maxCosine = Math.max(maxCosine, cosine); } if (maxCosine < 0.45) { maxArea = area; maxId = contours.indexOf(contour); } } } } }
绘制矩形的代码:
if (maxId >= 0) { Rect rect = Imgproc.boundingRect(contours.get(maxId)); Imgproc.rectangle(src, rect.tl(), rect.br(), new Scalar(255, 0, 0, .8), 4); }
之后将Mat转换为Bitmap并显示在ImageView中。
使用版本:OpenCV Android SDK 3.4.1、Android Studio 3.0.1
一、实现透视变换(含旋转矫正)
你当前用boundingRect得到的是轴对齐的矩形,但透视变换需要检测到的四边形的四个原始顶点,而不是轴对齐的边界框。以下是完整的实现步骤:
1. 获取并排序四边形顶点
轮廓检测得到的四个点顺序可能是随机的,必须先按左上、右上、右下、左下的顺序排序,否则透视变换会出现变形。添加一个排序方法:
private Point[] sortQuadPoints(List<Point> points) { Point[] sorted = new Point[4]; // 按x+y和排序,最小为左上,最大为右下 Collections.sort(points, (p1, p2) -> Double.compare(p1.x + p1.y, p2.x + p2.y)); sorted[0] = points.get(0); // 左上 sorted[2] = points.get(3); // 右下 // 剩余两点按x排序,小的为左下,大的为右上 List<Point> remaining = points.subList(1, 3); Collections.sort(remaining, (p1, p2) -> Double.compare(p1.x, p2.x)); sorted[3] = remaining.get(0); // 左下 sorted[1] = remaining.get(1); // 右上 return sorted; }
2. 计算透视变换矩阵并执行变换
在绘制矩形的代码后添加以下逻辑:
if (maxId >= 0) { // 获取检测到的四边形顶点 MatOfPoint contour = contours.get(maxId); MatOfPoint2f srcQuad = new MatOfPoint2f(contour.toArray()); // 对顶点排序 Point[] sortedPoints = sortQuadPoints(srcQuad.toList()); srcQuad = new MatOfPoint2f(sortedPoints); // 定义目标矩形的尺寸(根据你的需求调整) int targetWidth = 400; int targetHeight = 600; MatOfPoint2f dstQuad = new MatOfPoint2f( new Point(0, 0), new Point(targetWidth, 0), new Point(targetWidth, targetHeight), new Point(0, targetHeight) ); // 计算透视变换矩阵 Mat transformMatrix = Imgproc.getPerspectiveTransform(srcQuad, dstQuad); // 执行透视变换(自动矫正旋转和透视变形) Mat warpedMat = new Mat(); Imgproc.warpPerspective(src, warpedMat, transformMatrix, new Size(targetWidth, targetHeight)); // 将变换后的Mat转为Bitmap显示 Bitmap warpedBitmap = Bitmap.createBitmap(warpedMat.cols(), warpedMat.rows(), Bitmap.Config.ARGB_8888); Utils.matToBitmap(warpedMat, warpedBitmap); // 设置到你的ImageView // imageView.setImageBitmap(warpedBitmap); }
关键说明
getPerspectiveTransform:接收源四边形顶点和目标矩形顶点,生成3x3的变换矩阵。warpPerspective:使用变换矩阵将源图像映射到目标矩形,自动处理旋转、拉伸等透视变形。
二、矩形检测的优化建议
针对你的代码,这里有几个实用的优化点,既能提升检测精度又能降低性能消耗:
过滤小轮廓,减少计算量:在遍历轮廓时,先跳过面积过小的轮廓,避免无效计算:
double minArea = src.width() * src.height() * 0.05; // 过滤小于图像5%面积的轮廓 if (area < minArea) continue;调整Canny和阈值参数:当前Canny的双阈值都是50,可改为更合理的范围(比如30/100);adaptiveThreshold的blockSize必须是奇数,建议改成固定值:
// 修改Canny参数 Imgproc.Canny(gray0, gray, 30, 100, 3, true); // 修改adaptiveThreshold的blockSize为奇数 Imgproc.adaptiveThreshold(gray0, gray, thresholdLevel, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, Imgproc.THRESH_BINARY, 31, t); // 31是奇数,适合大多数场景减少Mat对象的创建:Android设备内存有限,尽量复用Mat对象,比如把循环内的
MatOfPoint2f temp = new MatOfPoint2f(...)提到循环外复用。使用RETR_EXTERNAL轮廓检索模式:只检测最外层轮廓,避免内部轮廓干扰:
Imgproc.findContours(gray, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);绘制原始四边形而非轴对齐矩形:方便调试,直观看到检测的真实轮廓:
if (maxId >= 0) { MatOfPoint2f approxCurve = new MatOfPoint2f(contours.get(maxId).toArray()); Imgproc.approxPolyDP(approxCurve, approxCurve, Imgproc.arcLength(approxCurve, true)*0.02, true); // 绘制多边形 Imgproc.polylines(src, Collections.singletonList(new MatOfPoint(approxCurve.toArray())), true, new Scalar(255,0,0,0.8),4); }优化角度判断阈值:当前
maxCosine < 0.45,这个值越小,对矩形的直角要求越严格,可根据场景调整为0.3,减少梯形误判。
内容的提问来源于stack exchange,提问作者Melih

