You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
pp6hrvtlm/util/ImageUtil.java

900 lines
34 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package com.yuxue.util;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.RotatedRect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.yuxue.constant.Constant;
import com.yuxue.enumtype.PlateColor;
/**
* 车牌图片处理工具类
* 将原图,经过算法处理,得到车牌的图块
* @author yuxue
* @date 2020-05-18 12:07
*/
public class ImageUtil {
private static String DEFAULT_BASE_TEST_PATH = "D:/PlateDetect/temp/";
static {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
}
// 车牌定位处理步骤该map用于表示步骤图片的顺序
private static Map<String, Integer> debugMap = Maps.newLinkedHashMap();
static {
debugMap.put("yuantu", 0); // 原图
debugMap.put("gaussianBlur", 0); // 高斯模糊
debugMap.put("gray", 0); // 图像灰度化
debugMap.put("sobel", 0); // Sobel 运算,得到图像的一阶水平方向导数
debugMap.put("threshold", 0); //图像二值化
debugMap.put("morphology", 0); // 图像闭操作
debugMap.put("clearInnerHole", 0); // 降噪
debugMap.put("clearSmallConnArea", 0); // 降噪
debugMap.put("clearAngleConn", 0); // 降噪
debugMap.put("clearHole", 0); // 降噪
debugMap.put("contours", 0); // 提取外部轮廓
debugMap.put("screenblock", 0); // 外部轮廓筛选
debugMap.put("crop", 0); // 切图
debugMap.put("resize", 0); // 切图resize
// 设置index 用于debug生成文件时候按名称排序
Integer index = 100;
for (Entry<String, Integer> entry : debugMap.entrySet()) {
entry.setValue(index);
index ++;
}
}
public static void main(String[] args) {
Instant start = Instant.now();
String tempPath = DEFAULT_BASE_TEST_PATH + "test/";
String filename = tempPath + "/100_yuantu.jpg";
filename = tempPath + "/100_yuantu1.jpg";
// filename = tempPath + "/109_crop_0.png";
// 读取原图
Mat src = Imgcodecs.imread(filename);
Boolean debug = true;
// 高斯模糊
Mat gsMat = ImageUtil.gaussianBlur(src, debug, tempPath);
// 灰度图
Mat gray = ImageUtil.gray(gsMat, debug, tempPath);
Mat sobel = ImageUtil.sobel(gray, debug, tempPath);
Mat threshold = ImageUtil.threshold(sobel, debug, tempPath);
// Mat scharr = ImageUtil.scharr(gray, debug, tempPath);
// Mat threshold = ImageUtil.threshold(scharr, debug, tempPath);
Mat morphology = ImageUtil.morphology(threshold, debug, tempPath);
List<MatOfPoint> contours = ImageUtil.contours(src, morphology, debug, tempPath);
Vector<Mat> rects = ImageUtil.screenBlock(src, contours, debug, tempPath);
PlateUtil.loadSvmModel("D:/PlateDetect/train/plate_detect_svm/svm2.xml");
PlateUtil.loadAnnModel("D:/PlateDetect/train/chars_recognise_ann/ann.xml");
Vector<Mat> dst = new Vector<Mat>();
PlateUtil.hasPlate(rects, dst, debug, tempPath);
System.err.println("识别到的车牌数量:" + dst.size());
dst.stream().forEach(inMat -> {
PlateColor color = PlateUtil.getPlateColor(inMat, true, debug, tempPath);
System.err.println(color.desc);
Vector<Mat> charMat = new Vector<Mat>();
PlateUtil.charsSegment(inMat, color, charMat, debug, tempPath);
});
/*String filename = tempPath + "/hsvMat_1590994270425.jpg";
Mat src = Imgcodecs.imread(filename);
Vector<Mat> charMat = new Vector<Mat>();
PlateUtil.charsSegment(src, PlateColor.BLUE, charMat, true, tempPath);*/
Instant end = Instant.now();
System.err.println("总耗时:" + Duration.between(start, end).toMillis());
// ImageUtil.rgb2Hsv(src, debug, tempPath);
// ImageUtil.getHSVValue(src, debug, tempPath);
}
/**
* 高斯模糊
* @param inMat
* @param debug
* @return
*/
public static final int DEFAULT_GAUSSIANBLUR_SIZE = 5;
public static Mat gaussianBlur(Mat inMat, Boolean debug, String tempPath) {
Mat dst = new Mat();
Imgproc.GaussianBlur(inMat, dst, new Size(DEFAULT_GAUSSIANBLUR_SIZE, DEFAULT_GAUSSIANBLUR_SIZE), 0, 0, Core.BORDER_DEFAULT);
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("gaussianBlur") + "_gaussianBlur.jpg", dst);
}
return dst;
}
/**
* 将图像进行灰度化
* @param inMat
* @param debug
* @param tempPath
* @return
*/
public static Mat gray(Mat inMat, Boolean debug, String tempPath) {
Mat dst = new Mat();
Imgproc.cvtColor(inMat, dst, Imgproc.COLOR_BGR2GRAY);
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("gray") + "_gray.jpg", dst);
}
inMat.release();
return dst;
}
/**
* 对图像进行Sobel 运算,得到图像的一阶水平方向导数
* @param inMat 灰度图
* @param debug
* @param tempPath
* @return
*/
public static final int SOBEL_SCALE = 1;
public static final int SOBEL_DELTA = 0;
public static final int SOBEL_X_WEIGHT = 1;
public static final int SOBEL_Y_WEIGHT = 0;
public static Mat sobel(Mat inMat, Boolean debug, String tempPath) {
Mat dst = new Mat();
Mat grad_x = new Mat();
Mat grad_y = new Mat();
Mat abs_grad_x = new Mat();
Mat abs_grad_y = new Mat();
// Sobel滤波 计算水平方向灰度梯度的绝对值
Imgproc.Sobel(inMat, grad_x, CvType.CV_16S, 1, 0, 3, SOBEL_SCALE, SOBEL_DELTA, Core.BORDER_DEFAULT);
Core.convertScaleAbs(grad_x, abs_grad_x); // 增强对比度
Imgproc.Sobel(inMat, grad_y, CvType.CV_16S, 0, 1, 3, SOBEL_SCALE, SOBEL_DELTA, Core.BORDER_DEFAULT);
Core.convertScaleAbs(grad_y, abs_grad_y);
grad_x.release();
grad_y.release();
// 计算结果梯度
Core.addWeighted(abs_grad_x, SOBEL_X_WEIGHT, abs_grad_y, SOBEL_Y_WEIGHT, 0, dst);
abs_grad_x.release();
abs_grad_y.release();
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("sobel") + "_sobel.jpg", dst);
}
return dst;
}
/**
* 对图像进行scharr 运算,得到图像的一阶水平方向导数
* @param inMat
* @param debug
* @param tempPath
* @return
*/
public static Mat scharr(Mat inMat, Boolean debug, String tempPath) {
Mat dst = new Mat();
Mat grad_x = new Mat();
Mat grad_y = new Mat();
Mat abs_grad_x = new Mat();
Mat abs_grad_y = new Mat();
//注意求梯度的时候我们使用的是Scharr算法sofia算法容易收到图像细节的干扰
//所谓梯度运算就是对图像中的像素点进行就导数运算,从而得到相邻两个像素点的差异值 by:Tantuo
Imgproc.Scharr(inMat, grad_x, CvType.CV_32F, 1, 0);
Imgproc.Scharr(inMat, grad_y, CvType.CV_32F, 0, 1);
//openCV中有32位浮点数的CvType用于保存可能是负值的像素数据值
Core.convertScaleAbs(grad_x, abs_grad_x);
Core.convertScaleAbs(grad_y, abs_grad_y);
//openCV中使用release()释放Mat类图像使用recycle()释放BitMap类图像
grad_x.release();
grad_y.release();
Core.addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst);
abs_grad_x.release();
abs_grad_y.release();
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("sobel") + "_sobel.jpg", dst);
}
return dst;
}
/**
* 对图像进行二值化。将灰度图像每个像素点有256个取值可能 0代表黑色255代表白色
* 转化为二值图像每个像素点仅有1和0两个取值可能
* @param inMat
* @param debug
* @param tempPath
* @return
*/
public static Mat threshold(Mat inMat, Boolean debug, String tempPath) {
Mat dst = new Mat();
Imgproc.threshold(inMat, dst, 100, 255, Imgproc.THRESH_OTSU + Imgproc.THRESH_BINARY);
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("threshold") + "_threshold.jpg", dst);
}
inMat.release();
return dst;
}
/**
* 使用闭操作。对图像进行闭操作以后,可以看到车牌区域被连接成一个矩形装的区域
* @param inMat
* @param debug
* @param tempPath
* @return
*/
// public static final int DEFAULT_MORPH_SIZE_WIDTH = 15;
// public static final int DEFAULT_MORPH_SIZE_HEIGHT = 3;
public static final int DEFAULT_MORPH_SIZE_WIDTH = 9;
public static final int DEFAULT_MORPH_SIZE_HEIGHT = 3;
public static Mat morphology(Mat inMat, Boolean debug, String tempPath) {
Mat dst = new Mat(inMat.size(), CvType.CV_8UC1);
Size size = new Size(DEFAULT_MORPH_SIZE_WIDTH, DEFAULT_MORPH_SIZE_HEIGHT);
Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, size);
Imgproc.morphologyEx(inMat, dst, Imgproc.MORPH_CLOSE, element);
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("morphology") + "_morphology0.jpg", dst);
}
// 填补内部孔洞,为了去除小连通区域的时候,降低影响
Mat a = clearInnerHole(dst, 8, 16, debug, tempPath);
// 去除小连通区域
Mat b = clearSmallConnArea(a, 1, 10, debug, tempPath);
// 按斜边去除
// Mat e = clearAngleConn(b, 5, debug, tempPath);
// 填补边缘孔洞
// Mat d = clearHole(a, 4, 2, debug, tempPath);
return b;
}
/**
* Find 轮廓 of possibles plates 求轮廓。求出图中所有的轮廓。
* 这个算法会把全图的轮廓都计算出来,因此要进行筛选。
* @param src 原图
* @param inMat morphology Mat
* @param debug
* @param tempPath
* @return
*/
public static List<MatOfPoint> contours(Mat src, Mat inMat, Boolean debug, String tempPath) {
List<MatOfPoint> contours = Lists.newArrayList();
Mat hierarchy = new Mat();
// 提取外部轮廓
// CV_RETR_EXTERNAL只检测最外围轮廓
// CV_RETR_LIST 检测所有的轮廓
// CV_CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点到contours向量内
Imgproc.findContours(inMat, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
if (debug) {
Mat result = new Mat();
src.copyTo(result); // 复制一张图,不在原图上进行操作,防止后续需要使用原图
// 将轮廓描绘到原图
Imgproc.drawContours(result, contours, -1, new Scalar(0, 0, 255, 255));
// 输出带轮廓的原图
Imgcodecs.imwrite(tempPath + debugMap.get("contours") + "_contours.jpg", result);
}
return contours;
}
/**
* 根据轮廓, 筛选出可能是车牌的图块
* @param src
* @param matVector
* @param debug
* @param tempPath
* @return
*/
public static final int DEFAULT_ANGLE = 30; // 角度判断所用常量
public static final int TYPE = CvType.CV_8UC3;
public static Vector<Mat> screenBlock(Mat src, List<MatOfPoint> contours, Boolean debug, String tempPath){
Vector<Mat> dst = new Vector<Mat>();
List<MatOfPoint> mv = Lists.newArrayList(); // 用于在原图上描绘筛选后的结果
for (int i = 0, j = 0; i < contours.size(); i++) {
MatOfPoint m1 = contours.get(i);
MatOfPoint2f m2 = new MatOfPoint2f();
m1.convertTo(m2, CvType.CV_32F);
// RotatedRect 该类表示平面上的旋转矩形,有三个属性: 矩形中心点(质心); 边长(长和宽); 旋转角度
// boundingRect()得到包覆此轮廓的最小正矩形, minAreaRect()得到包覆轮廓的最小斜矩形
RotatedRect mr = Imgproc.minAreaRect(m2);
double angle = Math.abs(mr.angle);
if (checkPlateSize(mr) && angle <= DEFAULT_ANGLE) { // 判断尺寸及旋转角度 ±30°排除不合法的图块
mv.add(contours.get(i));
Size rect_size = new Size((int) mr.size.width, (int) mr.size.height);
if (mr.size.width / mr.size.height < 1) { // 宽度小于高度
angle = 90 + angle; // 旋转90°
rect_size = new Size(rect_size.height, rect_size.width);
}
// 旋转角度,根据需要是否进行角度旋转
/*Mat img_rotated = new Mat();
Mat rotmat = Imgproc.getRotationMatrix2D(mr.center, angle, 1); // 旋转
Imgproc.warpAffine(src, img_rotated, rotmat, src.size()); // 仿射变换 考虑是否需要进行投影变换?
*/
// 切图
Mat img_crop = new Mat();
Imgproc.getRectSubPix(src, rect_size, mr.center, img_crop);
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("crop") + "_crop_" + j + ".png", img_crop);
}
// 处理切图,调整为指定大小
Mat resized = new Mat(Constant.DEFAULT_HEIGHT, Constant.DEFAULT_WIDTH, TYPE);
Imgproc.resize(img_crop, resized, resized.size(), 0, 0, Imgproc.INTER_CUBIC);
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("resize") + "_resize_" + j + ".png", resized);
j++;
}
dst.add(resized);
}
}
if (debug) {
Mat result = new Mat();
src.copyTo(result); // 复制一张图,不在原图上进行操作,防止后续需要使用原图
// 将轮廓描绘到原图
Imgproc.drawContours(result, mv, -1, new Scalar(0, 0, 255, 255));
// 输出带轮廓的原图
Imgcodecs.imwrite(tempPath + debugMap.get("screenblock") + "_screenblock.jpg", result);
}
return dst;
}
/**
* 对minAreaRect获得的最小外接矩形
* 判断面积以及宽高比是否在制定的范围内
* 黄牌、蓝牌、绿牌
* 国内车牌大小: 440mm*140mm宽高比 3.142857
* @param mr
* @return
*/
final static float DEFAULT_ERROR = 0.7f; // 宽高比允许70%误差
final static float DEFAULT_ASPECT = 3.142857f;
public static final int DEFAULT_VERIFY_MIN = 1;
public static final int DEFAULT_VERIFY_MAX = 30;
private static boolean checkPlateSize(RotatedRect mr) {
// 切图面积取值范围
int min = 44 * 14 * DEFAULT_VERIFY_MIN;
int max = 44 * 14 * DEFAULT_VERIFY_MAX;
// 切图横纵比取值范围;关键在于纵横比例
float rmin = DEFAULT_ASPECT - DEFAULT_ASPECT * DEFAULT_ERROR;
float rmax = DEFAULT_ASPECT + DEFAULT_ASPECT * DEFAULT_ERROR;
// 切图计算面积
int area = (int) (mr.size.height * mr.size.width);
// 切图宽高比
double r = mr.size.width / mr.size.height;
/*if (r < 1) { // 注释掉不处理width 小于height的图片
r = mr.size.height / mr.size.width;
}*/
return min <= area && area <= max && rmin <= r && r <= rmax;
}
/**
* rgb图像转换为hsv图像
* @param inMat
* @param debug
* @param tempPath
* @return
*/
public static Mat rgb2Hsv(Mat inMat, Boolean debug, String tempPath) {
// 转到HSV空间进行处理
Mat dst = new Mat();
Imgproc.cvtColor(inMat, dst, Imgproc.COLOR_BGR2HSV);
List<Mat> hsvSplit = Lists.newArrayList();
Core.split(dst, hsvSplit);
// 直方图均衡化是一种常见的增强图像对比度的方法,使用该方法可以增强局部图像的对比度,尤其在数据较为相似的图像中作用更加明显
Imgproc.equalizeHist(hsvSplit.get(2), hsvSplit.get(2));
Core.merge(hsvSplit, dst);
if (debug) {
// Imgcodecs.imwrite(tempPath + "hsvMat_"+System.currentTimeMillis()+".jpg", dst);
}
return dst;
}
/**
* 获取HSV中各个颜色所对应的H的范围
* HSV是一种比较直观的颜色模型所以在许多图像编辑工具中应用比较广泛这个模型中颜色的参数分别是色调H, Hue饱和度S,Saturation明度V, Value
* 1.PS软件时H取值范围是0-360S取值范围是0%-100%V取值范围是0%-100%)。
* 2.利用openCV中cvSplit函数的在选择图像IPL_DEPTH_32F类型时H取值范围是0-360S取值范围是0-10%-100%V取值范围是0-10%-100%)。
* 3.利用openCV中cvSplit函数的在选择图像IPL_DEPTH_8UC类型时H取值范围是0-180S取值范围是0-255V取值范围是0-255
* @param inMat
* @param debug
*/
public static void getHSVValue(Mat inMat, Boolean debug, String tempPath) {
int nRows = inMat.rows();
int nCols = inMat.cols();
Map<Integer, Integer> map = Maps.newHashMap();
for (int i = 0; i < nRows; ++i) {
for (int j = 0; j < nCols; j += 3) {
int H = (int)inMat.get(i, j)[0];
// int S = (int)inMat.get(i, j)[1];
// int V = (int)inMat.get(i, j)[2];
if(map.containsKey(H)) {
int count = map.get(H);
map.put(H, count+1);
} else {
map.put(H, 1);
}
}
}
Set<Integer> set = map.keySet();
Object[] arr = set.toArray();
Arrays.sort(arr);
for (Object key : arr) {
System.out.println(key + ": " + map.get(key));
}
return;
}
/**
* 计算最大内接矩形
* https://blog.csdn.net/cfqcfqcfqcfqcfq/article/details/53084090
* @param inMat
* @return
*/
public static Rect maxAreaRect(Mat threshold, Point point) {
int edge[] = new int[4];
edge[0] = (int) point.x + 1;//top
edge[1] = (int) point.y + 1;//right
edge[2] = (int) point.y - 1;//bottom
edge[3] = (int) point.x - 1;//left
boolean[] expand = { true, true, true, true};//扩展标记位
int n = 0;
while (expand[0] || expand[1] || expand[2] || expand[3]){
int edgeID = n % 4;
expand[edgeID] = expandEdge(threshold, edge, edgeID);
n++;
}
Point tl = new Point(edge[3], edge[0]);
Point br = new Point(edge[1], edge[2]);
return new Rect(tl, br);
}
/**
* @brief expandEdge 扩展边界函数
* @param img:输入图像单通道二值图深度为8
* @param edge 边界数组存放4条边界值
* @param edgeID 当前边界号
* @return 布尔值 确定当前边界是否可以扩展
*/
public static boolean expandEdge(Mat img, int edge[], int edgeID) {
int nc = img.cols();
int nr = img.rows();
switch (edgeID) {
case 0:
if (edge[0] > nr) {
return false;
}
for (int i = edge[3]; i <= edge[1]; ++i) {
if (img.get(edge[0], i)[0]== 255) {// 遇见255像素表明碰到边缘线
return false;
}
}
edge[0]++;
return true;
case 1:
if (edge[1] > nc) {
return false;
}
for (int i = edge[2]; i <= edge[0]; ++i) {
if (img.get(i, edge[1])[0] == 255)
return false;
}
edge[1]++;
return true;
case 2:
if (edge[2] < 0) {
return false;
}
for (int i = edge[3]; i <= edge[1]; ++i) {
if (img.get(edge[2], i)[0] == 255)
return false;
}
edge[2]--;
return true;
case 3:
if (edge[3] < 0) {
return false;
}
for (int i = edge[2]; i <= edge[0]; ++i) {
if (img.get(i, edge[3])[0] == 255)
return false;
}
edge[3]--;
return true;
default:
return false;
}
}
/**
* 清除白色区域的内部黑色孔洞
* rowLimit != colsLimit, 使用长方形比正方形好
* 该算法比较耗时
* @param inMat
* @param rowLimit
* @param colsLimit
* @param debug
* @param tempPath
* @return
*/
public static Mat clearInnerHole(Mat inMat, int rowLimit, int colsLimit, Boolean debug, String tempPath) {
Instant start = Instant.now();
int uncheck = 0, normal = 2, replace = 3, white = 255, black = 0;
Mat dst = new Mat(inMat.size(), CvType.CV_8UC1);
inMat.copyTo(dst);
// 初始化的图像全部为0未检查; 全黑图像
Mat label = new Mat(inMat.size(), CvType.CV_8UC1);
// 标记所有的白色区域
for (int i = 0; i < inMat.rows(); i++) {
for (int j = 0; j < inMat.cols(); j++) {
// 白色点较少,遍历白色点速度快
if (inMat.get(i, j)[0] == white && label.get(i, j)[0] == uncheck) { // 对于二值图0代表黑色255代表白色
label.put(i, j, normal); // 中心点
// 执行两次交换row 跟col
int condition = 0;
do {
int x1 = i;
int x2 = i + rowLimit >= inMat.rows() ? inMat.rows() - 1 : i + rowLimit;
int y1 = j;
int y2 = j + colsLimit >= inMat.cols() ? inMat.cols() - 1 : j + colsLimit ;
int count = 0;
// 遍历四条边
for (int k = x1; k < x2; k++) {
if(inMat.get(k, y1)[0] == black || inMat.get(k, y2)[0] == black) {
count++;
}
}
for (int k = y1; k < y2; k++) {
if(inMat.get(x1, k)[0] == black || inMat.get(x2, k)[0] == black) {
count++;
}
}
// 根据中心点+limit定位四个角生成一个矩形
// 矩形四条边都是白色,内部的黑点标记为 要被替换的对象
if(count == 0 ) {
for (int n = x1; n < x2; n++) {
for (int m = y1; m < y2; m++) {
if (inMat.get(n, m)[0] == black && label.get(n, m)[0] == uncheck) {
label.put(n, m, replace);
}
}
}
}
int ex = rowLimit;
rowLimit = colsLimit;
colsLimit = ex;
condition++;
} while (condition == 1);
}
}
}
for (int i = 0; i < inMat.rows(); i++) {
for (int j = 0; j < inMat.cols(); j++) {
if(label.get(i, j)[0] == replace) {
dst.put(i, j, white);
}
}
}
label.release();
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("clearInnerHole") + "_clearInnerHole.jpg", dst);
Instant end = Instant.now();
System.out.println("clearInnerHole执行耗时" + Duration.between(start, end).toMillis());
}
return dst;
}
/**
* 清除二值图像的黑洞
* 按矩形清理
* @param inMat 二值图像 0代表黑色255代表白色
* @param rowLimit 像素值
* @param colsLimit 像素值
* @param debug
* @param tempPath
*/
public static Mat clearHole(Mat inMat, int rowLimit, int colsLimit, Boolean debug, String tempPath) {
Instant start = Instant.now();
int uncheck = 0, normal = 2, replace = 3, white = 255, black = 0;
Mat dst = new Mat(inMat.size(), CvType.CV_8UC1);
inMat.copyTo(dst);
// 初始化的图像全部为0未检查; 全黑图像
Mat label = new Mat(inMat.size(), CvType.CV_8UC1);
// 标记所有的白色区域
for (int i = 0; i < inMat.rows(); i++) {
for (int j = 0; j < inMat.cols(); j++) {
if (inMat.get(i, j)[0] == white) { // 对于二值图0代表黑色255代表白色
label.put(i, j, normal); // 中心点
// 执行两次交换row 跟col
int condition = 0;
do {
int x1 = i;
int x2 = i + rowLimit >= inMat.rows() ? inMat.rows() - 1 : i + rowLimit;
int y1 = j;
int y2 = j + colsLimit >= inMat.cols() ? inMat.cols() - 1 : j + colsLimit ;
int count = 0;
if(inMat.get(x1, y1)[0] == white) {// 左上角
count++;
}
if(inMat.get(x1, y2)[0] == white) { // 左下角
count++;
}
if(inMat.get(x2, y1)[0] == white) { // 右上角
count++;
}
if(inMat.get(x2, y2)[0] == white) { // 右下角
count++;
}
// 根据中心点+limit定位四个角生成一个矩形
// 将四个角都是白色的矩形,内部的黑点标记为 要被替换的对象
if(count >=4 ) {
for (int n = x1; n < x2; n++) {
for (int m = y1; m < y2; m++) {
if (inMat.get(n, m)[0] == black && label.get(n, m)[0] == uncheck) {
label.put(n, m, replace);
}
}
}
}
int ex = rowLimit;
rowLimit = colsLimit;
colsLimit = ex;
condition++;
} while (condition == 1);
}
}
}
for (int i = 0; i < inMat.rows(); i++) {
for (int j = 0; j < inMat.cols(); j++) {
if(label.get(i, j)[0] == replace) {
dst.put(i, j, white); // 黑色替换成白色
}
}
}
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("clearHole") + "_clearHole.jpg", dst);
Instant end = Instant.now();
System.out.println("clearHole执行耗时" + Duration.between(start, end).toMillis());
}
return dst;
}
/**
* 清除二值图像的细小连接
* 按水平或者垂直方向清除
* @param inMat
* @param rowLimit
* @param colsLimit
* @param debug
* @param tempPath
* @return
*/
public static Mat clearSmallConnArea(Mat inMat, int rowLimit, int colsLimit, Boolean debug, String tempPath) {
Instant start = Instant.now();
int uncheck = 0, normal = 2, replace = 3, white = 255, black = 0;
Mat dst = new Mat(inMat.size(), CvType.CV_8UC1);
inMat.copyTo(dst);
// 初始化的图像全部为0未检查; 全黑图像
Mat label = new Mat(inMat.size(), CvType.CV_8UC1);
// 标记所有的白色区域
for (int i = 0; i < inMat.rows(); i++) {
for (int j = 0; j < inMat.cols(); j++) {
if (inMat.get(i, j)[0] == black) { // 对于二值图0代表黑色255代表白色
label.put(i, j, normal); // 中心点
// 执行两次交换row 跟col
int condition = 0;
do {
int x1 = i;
int x2 = i + rowLimit >= inMat.rows() ? inMat.rows() - 1 : i + rowLimit;
int y1 = j;
int y2 = j + colsLimit >= inMat.cols() ? inMat.cols() - 1 : j + colsLimit ;
int count = 0;
if(inMat.get(x1, y1)[0] == black) {// 左上角
count++;
}
if(inMat.get(x1, y2)[0] == black) { // 左下角
count++;
}
if(inMat.get(x2, y1)[0] == black) { // 右上角
count++;
}
if(inMat.get(x2, y2)[0] == black) { // 右下角
count++;
}
// 根据 中心点+limit定位四个角生成一个矩形
// 将四个角都是黑色的矩形,内部的白点标记为 要被替换的对象
if(count >= 4) {
for (int n = x1; n < x2; n++) {
for (int m = y1; m < y2; m++) {
if (inMat.get(n, m)[0] == white && label.get(n, m)[0] == uncheck) {
label.put(n, m, replace);
}
}
}
}
int ex = rowLimit;
rowLimit = colsLimit;
colsLimit = ex;
condition++;
} while (condition == 1);
}
}
}
for (int i = 0; i < inMat.rows(); i++) {
for (int j = 0; j < inMat.cols(); j++) {
if(label.get(i, j)[0] == replace) {
dst.put(i, j, black); // 白色替换成黑色
}
}
}
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("clearSmallConnArea") + "_clearSmallConnArea.jpg", dst);
Instant end = Instant.now();
System.out.println("clearSmallConnArea执行耗时" + Duration.between(start, end).toMillis());
}
return dst;
}
/**
* 清除二值图像的细小连接
* 按45度斜边清除
* @param inMat
* @param limit
* @param angle
* @param debug
* @param tempPath
* @return
*/
public static Mat clearAngleConn(Mat inMat, int limit, Boolean debug, String tempPath) {
Instant start = Instant.now();
int uncheck = 0, normal = 2, replace = 3, white = 255, black = 0;
Mat dst = new Mat(inMat.size(), CvType.CV_8UC1);
inMat.copyTo(dst);
// 初始化的图像全部为0未检查; 全黑图像
Mat label = new Mat(inMat.size(), CvType.CV_8UC1);
// 标记所有的白色区域
for (int i = 0; i < inMat.rows(); i++) {
for (int j = 0; j < inMat.cols(); j++) {
if (inMat.get(i, j)[0] == black) { // 对于二值图0代表黑色255代表白色
label.put(i, j, normal); // 中心点
int x1 = i;
int x2 = i + limit >= inMat.rows() ? inMat.rows() - 1 : i + limit;
int y1 = j;
int y2 = j + limit >= inMat.cols() ? inMat.cols() - 1 : j + limit ;
// 根据 中心点+limit定位四个角生成一个矩形
// 将2个角都是黑色的线内部的白点标记为 要被替换的对象
// 【\】 斜对角线
if(inMat.get(x1, y1)[0] == black && inMat.get(x2, y2)[0] == black) {
for (int n = x1, m = y1; n < x2 && m < y2; n++, m++) {
if (inMat.get(n, m)[0] == white && label.get(n, m)[0] == uncheck) {
label.put(n, m, replace);
}
}
}
if(inMat.get(x1, y2)[0] == black && inMat.get(x2, y1)[0] == black) {
// 【/】 斜对角线
for (int n = x1, m = y2; n < x2 && m > y1; n++, m--) {
if (inMat.get(n, m)[0] == white && label.get(n, m)[0] == uncheck) {
label.put(n, m, replace);
}
}
}
}
}
}
// 白色替换成黑色
for (int i = 0; i < inMat.rows(); i++) {
for (int j = 0; j < inMat.cols(); j++) {
if(label.get(i, j)[0] == replace) {
dst.put(i, j, black);
}
}
}
if (debug) {
Imgcodecs.imwrite(tempPath + debugMap.get("clearAngleConn") + "_clearAngleConn.jpg", dst);
Instant end = Instant.now();
System.out.println("clearAngleConn执行耗时" + Duration.between(start, end).toMillis());
}
return dst;
}
}