实例1-从大图片中识别出胶体金卡的顺序不固定
实例2将解决
获取-0-3张卡片图像(从原图上,通过轮廓识别,获取0-3张卡片图像)
bitmap = mSurfaceView.readPicture(img_cha1); //获取原图片 Utils.bitmapToMat(bitmap, mat); Mat binaryImage = myOpenCV.getBinaryImage(mat); //获取---二值化图像 myOpenCV.showImage(binaryImage, img_big0); List<Bitmap> listBitmaps = myOpenCV.getContours_multiCards(bitmap,binaryImage,img_big1); Log.e(TAG, "auto: 从摄像头照片中获取卡的个数 = "+ listBitmaps.size());
方法
//===================================================== public List<Bitmap> getContours_multiCards(Bitmap bitmapRes,Mat binaryImage, ImageView v){ Log.e(TAG, "getContours_multiCards---从图片( Mat二值图片)中获取多张胶体金卡 "); try{ //查找轮廓---获取轮廓(从原图上,通过轮廓识别,获取0-3张卡片图像) // binaryImage必须为二值化图像(必须是8位单通道二值图像(通常由Canny、阈值分割等得到)) // bitmap 原摄像头拍下的图片 //-------------------------------------------------------获取所有轮廓 List<MatOfPoint> contours = new ArrayList<>(); Mat hierarchy = new Mat(); Imgproc.findContours(binaryImage, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE); Log.e(TAG, " 获取所有轮廓总个数 = "+ contours.size()); //------------------------------------------------------- // Log.e(TAG, "=======================显示轮廓部分---调试用---可屏蔽============================== "); // Mat resultMat = new Mat(binaryImage.rows(),binaryImage.cols(), CvType.CV_8UC4); // for (MatOfPoint contour : contours) { // // double area = Imgproc.contourArea(contour); // if((area > 100000)&&(area < 240000)){ //根据 card面积显示图像轮廓 // // Rect rect = Imgproc.boundingRect(contour);// 获取这个四边形的边界矩形 // Imgproc.rectangle(resultMat, rect.tl(), rect.br(), new Scalar(255,0,0,255), 3); //红色 // Log.e(TAG, "测试轮廓 宽 * 高 = "+ (rect.br().x - rect.tl().x)+ " " + (rect.br().y - rect.tl().y)+" 面积 = "+Imgproc.contourArea(contour)); // } // } // if(v != null)showImage(resultMat,v); Log.e(TAG, "===================================================== "); //----------------------------------------------- //----------------------------------------------- List<Rect> listRect = new ArrayList<>(); List<MatOfPoint> listsquares= new ArrayList<>(); // //------------遍历轮廓 double maxArea = 0; Rect roiRect = null; for (MatOfPoint contour : contours) { MatOfPoint2f approxCurve = new MatOfPoint2f(); //创建浮点型轮廓容器的标准写法,用于存储多边形逼近的结果 MatOfPoint2f contour2f = new MatOfPoint2f(contour.toArray()); //整数点集转换为 浮点型点集 double arcLength = Imgproc.arcLength(contour2f, true); //计算轮廓周长 Imgproc.approxPolyDP(contour2f, approxCurve, 0.02 * arcLength, true); //将轮廓近似为顶点更少的多边形 RotatedRect minRect = Imgproc.minAreaRect(approxCurve); Log.e(TAG, "minRect.toString() = "+ minRect.toString()); maxArea = Imgproc.contourArea(contour); if((maxArea > 100000)&&(maxArea < 240000)){ //面积 来判度胶体金卡 //Log.e(TAG, "approxCurve.total()轮廓边的数量 = "+ approxCurve.total()); // 如果4边形-8边都是为矩形 if ((approxCurve.total() >= 4)&&(approxCurve.total() <= 8)){ //if ((approxCurve.total() == 4)){ roiRect = Imgproc.boundingRect(contour);// 获取这个四边形的边界矩形 double w = roiRect.br().x - roiRect.tl().x; double h = roiRect.br().y - roiRect.tl().y; Log.e(TAG, "getContours_multiCards: = " +roiRect.tl() +" * "+ roiRect.br() + " = "+ maxArea +" 宽 * 高 = " + w + " * " + h +" "+ w * h); //卡的面积范围140000-175000; if((w > 200 && w < 300)&&(h > 600 && h < 800)){ Log.e(TAG, "getContours_multiCards: 已选方框 = " +roiRect.tl() +" * "+ roiRect.br() + " = "+ maxArea ); listRect.add(roiRect); } //宽高 来判度胶体金卡 } } } Log.e(TAG, "step2: 方形个数 = "+ listRect.size()); //---------------------------------------- List<Mat> listMats = new ArrayList<>(); List<Bitmap> listBitmaps = new ArrayList<>(); for(int i = 0; i < listRect.size(); i++){ Rect rect = listRect.get(i); Bitmap b = Bitmap.createBitmap(bitmapRes,rect.x + 10,rect.y + 5, rect.width - 10, rect. height - 10); listBitmaps.add(b); } return listBitmaps; }catch (Exception e){ return null; } }public Mat getBinaryImage(Mat mat){ Log.e(TAG, "getBinaryImage:------------------获取---二值化图像" ); Mat binaryImage = new Mat(); try { //--------------------------------------------------- // 2. 转换为灰度图 Mat grayImage = new Mat(); Imgproc.cvtColor(mat, grayImage, Imgproc.COLOR_BGR2GRAY);//灰色图片 //-------------------------------------------------- // 3. 高斯模糊去噪 Mat blurredImage = new Mat(); Imgproc.GaussianBlur(grayImage, blurredImage, new Size(5, 5), 0); //-------------------------------------------------- // 4. 图像二值化 (阈值处理) Imgproc.threshold(blurredImage, binaryImage, 0, 255, Imgproc.THRESH_BINARY_INV + Imgproc.THRESH_OTSU); //Imgproc.threshold(blurredImage, binaryImage, 0, 255, Imgproc.THRESH_TOZERO); //----------------------------------------------- ///grayImage.release(); blurredImage.release(); return binaryImage; }catch (Exception e){ Log.e(TAG, "getBinaryImage:---错误" ); } return null; }实例 2 从大图片中识别出胶体金卡的顺序是固定的(推荐)
List<Bitmap> listBitmaps = myOpenCV.getContours_getMultiCards(bitmap,null);
@RequiresApi(api = Build.VERSION_CODES.N) public List<Bitmap> getContours_getMultiCards(Bitmap bitmapsrc, ImageView v){ Log.e(TAG, "getContours_getMultiCards==============================获取-----多张胶体金卡从图片 "); Mat mat = new Mat(); Utils.bitmapToMat(bitmapsrc,mat); Mat grayImage = new Mat(); Imgproc.cvtColor(mat, grayImage, Imgproc.COLOR_BGR2GRAY);//灰色图片 //-------------------------------------------------- // 3. 高斯模糊去噪 Mat blurredImage = new Mat(); Imgproc.GaussianBlur(grayImage, blurredImage, new Size(5, 5), 0); //-------------------------------------------------- // 4. 图像二值化 (阈值处理) Mat binaryImage = new Mat(); Imgproc.threshold(blurredImage, binaryImage, 0, 255, Imgproc.THRESH_BINARY_INV + Imgproc.THRESH_OTSU); //----------------------------------------------- ///grayImage.release(); mat.release(); grayImage.release(); blurredImage.release(); List<Bitmap> listBitmaps = new ArrayList<>(); try{ //查找轮廓---获取轮廓(从原图上,通过轮廓识别,获取0-3张卡片图像) // binaryImage必须为二值化图像(必须是8位单通道二值图像(通常由Canny、阈值分割等得到)) // bitmap 原摄像头拍下的图片 //-------------------------------------------------------获取所有轮廓 List<MatOfPoint> contours = new ArrayList<>(); Mat hierarchy = new Mat(); Imgproc.findContours(binaryImage, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE); Log.e(TAG, "getContours_getMultiCards 获取所有轮廓总个数 = "+ contours.size()); //------------------------------------------------------- // if(v != null) { // // Log.e(TAG, "=======================显示轮廓部分---调试用---可屏蔽============================== "); // Mat resultMat = new Mat(binaryImage.rows(), binaryImage.cols(), CvType.CV_8UC4); // for (MatOfPoint contour : contours) { // // double area = Imgproc.contourArea(contour); // if ((area > 100000) && (area < 240000)) { //根据 card面积显示图像轮廓 // // Rect rect = Imgproc.boundingRect(contour);// 获取这个四边形的边界矩形 // Imgproc.rectangle(resultMat, rect.tl(), rect.br(), new Scalar(255, 0, 0, 255), 3); //红色 // Log.e(TAG, "测试轮廓 宽 * 高 = " + (rect.br().x - rect.tl().x) + " " + (rect.br().y - rect.tl().y) + " 面积 = " + Imgproc.contourArea(contour)); // } // } // if (v != null) showImage(resultMat, v); // } //------------------------------------------------------ //从大图片中识别出胶体金卡的顺序是固定的 List<MatOfPoint> validContours = new ArrayList<>(); for (MatOfPoint contour : contours) { double area = Imgproc.contourArea(contour); //卡的面积范围140000-175000; //面积 来判度胶体金卡 if ((area > 100000) && (area < 240000)) { //阈值请根据你的图像调整 validContours.add(contour); } } List<MatOfPoint> sortedContours = sortContoursByPosition(validContours, 20); Log.e(TAG, "getContours_multiCards: sortedContours.size() = " +sortedContours.size()); //------------------------------------------------------ Rect rect = null; for(int i = 0; i < sortedContours.size();i++){ MatOfPoint contour1 = sortedContours.get(i); MatOfPoint2f approxCurve = new MatOfPoint2f(); //创建浮点型轮廓容器的标准写法,用于存储多边形逼近的结果 MatOfPoint2f contour2f = new MatOfPoint2f(contour1.toArray()); //整数点集转换为 浮点型点集 double arcLength = Imgproc.arcLength(contour2f, true); //计算轮廓周长 Imgproc.approxPolyDP(contour2f, approxCurve, 0.02 * arcLength, true); //将轮廓近似为顶点更少的多边形 //Log.e(TAG, "approxCurve.total()轮廓边的数量 = "+ approxCurve.total()); if ((approxCurve.total() >= 4)&&(approxCurve.total() <= 8)){ // 如果4边形-8边都是为矩形 rect = Imgproc.boundingRect(contour1);// 获取这个四边形的边界矩形 if((rect.width > 200 && rect.width < 300) && (rect.height > 600 && rect.height < 800)){ //宽高 来判度胶体金卡 // Log.e(TAG, "getContours_multiCards: 已卡选方框 = " +roiRect.tl() +" * "+ roiRect.br() + " = "+ maxArea ); Bitmap b = Bitmap.createBitmap(bitmapsrc,rect.x + 10, rect.y + 5, rect.width - 10, rect.height - 10); listBitmaps.add(b); } } } if(listBitmaps.size() > 1){Collections.reverse(listBitmaps);} //list倒叙 return listBitmaps; }catch (Exception e){ return null; } }//========================================================================== //========================================================================== //========================================================================== /** * 对轮廓列表进行排序:先按Y坐标(行)分组,再按X坐标(列)排序。 * @param contours 待排序的轮廓列表 * @param yTolerance Y坐标容差(像素),差值小于此值的轮廓视为同一行 * @return 排序后的轮廓列表 */ @RequiresApi(api = Build.VERSION_CODES.N) public List<MatOfPoint> sortContoursByPosition(List<MatOfPoint> contours, int yTolerance) { if (contours == null || contours.size() <= 1) { return contours; } // 1. 为每个轮廓计算边界框,并封装成列表 List<ContourWithRect> contourRects = new ArrayList<>(); for (MatOfPoint contour : contours) { Rect rect = Imgproc.boundingRect(contour); contourRects.add(new ContourWithRect(contour, rect)); } // 2. 按Y坐标初步排序,以便分组 contourRects.sort((a, b) -> Integer.compare(a.rect.y, b.rect.y)); // 3. 根据Y容差分组(将Y坐标接近的轮廓归为同一行) List<List<ContourWithRect>> rows = new ArrayList<>(); List<ContourWithRect> currentRow = new ArrayList<>(); int currentRowBaseY = -1; for (ContourWithRect item : contourRects) { if (currentRowBaseY == -1 || Math.abs(item.rect.y - currentRowBaseY) <= yTolerance) { // 属于当前行 currentRow.add(item); // 更新当前行的基准Y坐标(可选用平均值,这里简单使用第一个元素的Y) if (currentRowBaseY == -1) { currentRowBaseY = item.rect.y; } } else { // 新的一行开始 rows.add(new ArrayList<>(currentRow)); currentRow.clear(); currentRow.add(item); currentRowBaseY = item.rect.y; } } if (!currentRow.isEmpty()) { rows.add(currentRow); } // 4. 对每一行内的轮廓,按X坐标从左到右排序,然后合并结果 List<MatOfPoint> sortedContours = new ArrayList<>(); for (List<ContourWithRect> row : rows) { row.sort((a, b) -> Integer.compare(a.rect.x, b.rect.x)); for (ContourWithRect item : row) { sortedContours.add(item.contour); } } return sortedContours; } // 辅助内部类 private static class ContourWithRect { MatOfPoint contour; Rect rect; ContourWithRect(MatOfPoint contour, Rect rect) { this.contour = contour; this.rect = rect; } } //========================================================================== //========================================================================== //==========================================================================在实例 2中有时会出现问题,原测程序正常,移植到实际项目中会出现问题(共2处)
解决第1处
****************************************************************************************
// 2. 按Y坐标初步排序,以便分组 把contourRects.sort((a, b) -> Integer.compare(a.rect.y, b.rect.y)); 改为以下 // 2. 按Y坐标初步排序(使用匿名内部类) Collections.sort(contourRects, new Comparator<ContourWithRect>() { @Override public int compare(ContourWithRect a, ContourWithRect b) { return Integer.compare(a.rect.y, b.rect.y); } });解决第1处
把row.sort((a, b) -> Integer.compare(a.rect.x, b.rect.x));改为// 使用匿名内部类代替 Lambda Collections.sort(row, new Comparator<ContourWithRect>() { @Override public int compare(ContourWithRect a, ContourWithRect b) { return Integer.compare(a.rect.x, b.rect.x); } });