简介
这是在Android端上面做的,所以就结了个logcat的图将,opencv3.4.1。
大致内容有,颜色、形状、边长、面积、边长比 的求法。
识别的是图一,中间进行了内容的提取,这里只给出从图二开始识别的过程。
具体提取图二过程见另一篇文章
图二是根据图一手裁的,具体程序裁完没有外边的黑边。
https://blog.csdn.net/qq_43701766/article/details/105877653
考虑到外界光照的影响,所识别的图像的曝光不平均的因素,二值化的时候采用的是自适应二值的方法,所以二值那张图片上会有一些多余的小的白斑,识别图形的时候做了一定的处理。
为了方便大家测试,代码中的logcat和图片保存部分代码已经删除。
具体调用方法:
public static void DisposeTFT(Bitmap bt){
// 这是个用来裁剪图片的 将图一 转换到 图二的方法 若不需要 可以省去 具体实现 上边有链接
Mat image = Cut_Out_TFT(bt); // 裁剪图片
image = cvut.picDown(image); // 图片缩小
Mat declarMat = cvut.cloneMat(image); // 复制图像
cvut.toDilate(image, 3, 3, 1);// 膨胀
cvut.toGrayMat(image);// 灰度化
Mat binary = cvut.cloneMat(image);
// 自适应二值化
// 参数4 : ADAPTIVE_THRESH_MEAN_C 平均值
// 参数4 : ADAPTIVE_THRESH_GAUSSIAN_C 高斯加权
// blockSize : 域块大小 用来计算区域阈值 一般为奇数
// C : 计算后减去的值
Imgproc.adaptiveThreshold(binary, binary,255, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,
Imgproc.THRESH_BINARY_INV, 55, 5);
cvut.coverBackGroundToBlack(binary); // 背景变黑 没什么用
List<MatOfPoint> contoursList = cvut.findContoursList(binary);// 找到轮廓
int i = 0;
List<MatOfPoint> approxCurves = new ArrayList<>(); // 存取拟合多边形的点集
// 轮廓处理
for(MatOfPoint point : contoursList) {
if (Imgproc.contourArea(point) < 300) continue; // 判断面积小于一定值时不是所需轮廓 过滤 自适应二值化的弊端
Mat mat = Mat.zeros(declarMat.rows(), declarMat.cols(), CvType.CV_8UC3);
MatOfPoint2f curvesPoint = new MatOfPoint2f(point.toArray()); // 将MatOfPoint转为MatOfPoint2f
MatOfPoint2f approxCurvesPoint = new MatOfPoint2f();
Imgproc.approxPolyDP(curvesPoint,approxCurvesPoint,0,true); // 拟合
MatOfPoint newPoint = new MatOfPoint(approxCurvesPoint.toArray()); // 将MatOfPoint2f转为MatOfPoint
approxCurves.add(newPoint); // 添加轮廓
Imgproc.drawContours(mat,approxCurves,i,new Scalar(0,255,0),1,8); // 绘制拟合后的多边形
Point[] points = cvut.findp(mat);// 找到描边点
Point[] checkedPoints = cvut.checkPoint(points);// 清除同一条直线上的点
// 这里是颜色与形状的处理方法
String shape = cvut.findShape(checkedPoints, approxCurvesPoint);// 确定形状
String color = cvut.findColor(declarMat, checkedPoints); // 颜色
}
另一个类里的东西:
/**
* 图片缩小
* INTER_AREA 弊端:图片较大时 处理速度偏慢
*/
public static Mat picDown(Mat bit){
Mat Sbit = new Mat();
Imgproc.resize(bit, Sbit, new Size( bit.width()*0.5, bit.height()*0.5), Imgproc.INTER_AREA);
Imgproc.resize(Sbit, Sbit, new Size( Sbit.width()*0.5, Sbit.height()*0.5), Imgproc.INTER_AREA);
return Sbit;
}
/**
* 复制 Mat
*/
public Mat cloneMat(Mat mat) {
return mat.clone();
}
/**
* 膨胀
*/
public void toDilate(Mat mat, int i, int j, int iterations) {
Imgproc.dilate(mat, mat, Imgproc.getStructuringElement(
Imgproc.MORPH_RECT, new Size(i, j)), new Point(-1, -1),
iterations);
}
/**
* 灰度化图像
*/
public void toGrayMat(Mat from) {
Imgproc.cvtColor(from, from, Imgproc.COLOR_BGR2GRAY);
}
/**
* 填充杂色
*/
public void coverBackGroundToBlack(Mat mat) {
final double blackPixle[] = { 0.0 };
for (int y = 0; y < mat.height(); y++) {
for (int x = 0; x < mat.width(); x++) {
double pixle[] = mat.get(y, x);
if (pixle[0] == 255.0) {// 如果是白色
mat.put(y, x, blackPixle);
} else {// 遇到黑色
break;
}
}
for (int x = mat.width() - 1; x > 0; x--) {
double pixle[] = mat.get(y, x);
if (pixle[0] == 255.0) {// 如果是白色
mat.put(y, x, blackPixle);
} else {// 遇到黑色
break;
}
}
}
}
/**
* 获得角点 传入单调且不能是二值化的图像
*/
public Point[] findp(Mat mat) {
MatOfPoint corners = new MatOfPoint();
this.toGrayMat(mat);
Imgproc.goodFeaturesToTrack(mat, corners, 50, 0.01, 10,
new Mat(), 3, true, 0.04);
Point[] pCorners = corners.toArray();
return pCorners;
}
/**
* 合并直线上的点
*/
public Point[] checkPoint(Point[] points) {
int lastLength = -1;
int thisLength = 0;
Point[] lp = points;
Point[] np;
while (true) {
np = checkPointOnce(lp);
thisLength = np.length;
if (thisLength == lastLength) {
break;
}
lastLength = thisLength;
lp = np;
}
return np;
}
/**
* 删除三点中处于中间的点
*/
private int deleteMiddlePointToNull(Point p1, Point p2, Point p3) {
double a = p1.x + p1.y;
double b = p2.x + p2.y;
double c = p3.x + p3.y;
if ((a > b && b > c) || (a < b && b < c)) {// b在中间
return 2;
} else if ((c > a && a > b) || (c < a && a < b)) {// a在中间
return 1;
} else {
return 3;
}
}
/**
* 合并直线上的点
*/
private Point[] checkPointOnce(Point[] points) {
int length = points.length;
boolean flag = false;// 是否找到可删除点
if (length < 4) {
return points;// 如果小于四个点 免了判断
}
label: for (int i = 0; i < length; i++) {// 得到点1
for (int j = 0; j < length; j++) {// 得到点2
if (j == i) {
continue;
}
for (int k = 0; k < length; k++) {// 得到点3
if (k == j || k == i) {
continue;
}
// int slope = 0;//斜率
double d1 = twoPointsAngel(points[i], points[j]);// i,j直线角度
double d2 = twoPointsAngel(points[i], points[k]);// i,k直线角度
double angelMin = d1 - d2;
if (Math.abs(angelMin) < 10) {// 如果倾角非常接近,删除中间的点
int needDelete = deleteMiddlePointToNull(points[i],
points[j], points[k]);
if (needDelete == 1) {
points[i] = null;
} else if (needDelete == 2) {
points[j] = null;
} else if (needDelete == 3) {
points[k] = null;
}
flag = true;
break label;
}
}
}
}
if (flag) {
Point[] newPoints = new Point[length - 1];
int index = 0;
for (Point p : points) {// 准备一个没有空值的新数组
if (null != p) {
newPoints[index] = p;
index++;
}
}
return newPoints;
} else {
return points;
}
}
/**
* 查找轮廓并返回轮廓数组 最好传入阈值图
*/
public List<MatOfPoint> findContoursList(Mat mat) {
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Mat hierarchy = new Mat();
Imgproc.findContours(mat, contours, hierarchy, Imgproc.RETR_LIST,
Imgproc.CHAIN_APPROX_SIMPLE);
return contours;
}
接下来是颜色和形状识别的两个方法
形状检测
根据轮廓的角点数量、位置、面积 等因素判断形状。
/**
* 形状识别
*/
public String findShape(Point[] checkedPoints, MatOfPoint2f line) {
int length = checkedPoints.length;
if (length >= 5){
Point X = new Point();
float[] R = new float[1];
MatOfPoint Line = new MatOfPoint(line.toArray());
// X : 圆心坐标 R : 半径
Imgproc.minEnclosingCircle(line, X, R); // 最小外接圆 为什么需要传一个数组得到半径????
if (Imgproc.contourArea(Line) > (R[0]*R[0]*3.1415926*0.75)){
// 面积面积若大于最小外接圆的 0.75 则认为是圆
return "圆形";
}else {
// 待添加五角星的判断
return "五角星";
}
}
if (length < 3) {
return "ShapeError";
} else if (length == 3) {
return "三角";
} else if (length == 4) {// 四边形
double d1 = twoPointsDistance(checkedPoints[0], checkedPoints[1]);
double d2 = twoPointsDistance(checkedPoints[0], checkedPoints[2]);
double d3 = twoPointsDistance(checkedPoints[0], checkedPoints[3]);
Point[] p = new Point[2];
// 找与第一个点相邻的两个点(舍弃最远的那个点)
if (d1 > d2 && d1 > d3) {// d1最大,舍弃下标1
p[0] = checkedPoints[2];
p[1] = checkedPoints[3];
} else if (d2 > d1 && d2 > d3) {// d2最大,舍弃下标2
p[0] = checkedPoints[1];
p[1] = checkedPoints[3];
} else {
p[0] = checkedPoints[1];
p[1] = checkedPoints[2];
}// 现在数组p中是两个最近的点
double angelL1 = twoPointsAngel(checkedPoints[0], p[0]);
double angelL2 = twoPointsAngel(checkedPoints[0], p[1]);
double angelP = Math.abs(angelL1 - angelL2);
if (angelP > 80 && angelP < 100) {// 直角
double dis1 = twoPointsDistance(checkedPoints[0], p[0]);
double dis2 = twoPointsDistance(checkedPoints[0], p[1]);
double distanceRatio = dis1 / dis2;
if (distanceRatio > 0.80 && distanceRatio < 1.20) {
return "正方";
} else {
return "长方";
}
} else {
// 若不是直角 四边相等的是菱形
// 有一对对边不等的是梯形
// 剩下的是平行四边形
// 这个四边形识别比较多 勤快的可以把它单独分离出去一个方法
checkedPoints = pointSort2(checkedPoints);
double l1 = twoPointsDistance(checkedPoints[0], checkedPoints[1]); // 上边
double l2 = twoPointsDistance(checkedPoints[0], checkedPoints[3]); // 左边
double l3 = twoPointsDistance(checkedPoints[2], checkedPoints[1]); // 右边
double l4 = twoPointsDistance(checkedPoints[2], checkedPoints[3]); // 下边
double dis1 = l1 / l2;
double dis2 = l2 / l3; // 梯形需要的左右对边比
double dis3 = l1 / l4; // 梯形需要的上下对边比
if (dis1 > 0.8 && dis1 < 1.2 && dis2 > 0.8 && dis2 < 1.2 && dis3 > 0.8 && dis3 < 1.2){
return "菱形";
} else if (dis2 > 1.2 || dis3 > 1.2){
return "梯形";
}else {
return "平行四边形";
}
}
} else {
return "ShapeError";
}
}
识别形状用到的方法:
两点间距离、直线夹角
角点排序
/**
* 求两点之间距离
*/
private double twoPointsDistance(Point p1, Point p2) {
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}
/**
* 求两点所在直线水平夹角
*/
private double twoPointsAngel(Point p1, Point p2) {
if (p1.y == p2.y) {
p1.y += 0.01;
}
return Math.toDegrees(Math.atan((p1.x - p2.x) / (p1.y - p2.y)));
}
// 角点排序... 左上 右上 右下 左下
// 用于四边形识别
public static Point[] pointSort2(Point[] LinePoints){
Point t;
// 冒泡排序 Y 小的放前
for (int j = 0; j < 3; j++){
for (int i = 0; i < 4; i++) {
if (LinePoints[i].y > LinePoints[i + 1].y) {
t = LinePoints[i];
LinePoints[i] = LinePoints[i + 1];
LinePoints[i + 1] = t;
}
}
}
if (LinePoints[0].x > LinePoints[1].x){
t = LinePoints[0];
LinePoints[0] = LinePoints[1];
LinePoints[1] = t;
}
if (LinePoints[2].x < LinePoints[3].x){
t = LinePoints[2];
LinePoints[2] = LinePoints[3];
LinePoints[3] = t;
}
return LinePoints;
}
颜色识别
主要根据图像中心点的RGB进行颜色判断。
/**
* 颜色识别
*/
public String findColor(Mat colorfulMat, Point[] checkedPoints){
HashMap<String, double[]> CvColor = CvColor();
Point centerPoint = getCenterPoint(checkedPoints); // 取得轮廓中心点的坐标
double[] RGB = colorfulMat.get((int)centerPoint.y, (int)centerPoint.x);
double[] cvRGB;
Set<String> set = CvColor.keySet();
int r = 15, g = 40, b = 40; // 容忍度
for (String string : set){
cvRGB = CvColor.get(string);
if (colorMiss(RGB, cvRGB, r, g, b)){
return string;
}
}
// 若颜色识别失败 向左上移动两个像素 重复
Point Point = new Point((centerPoint.x - 2), (centerPoint.y -2));
RGB = colorfulMat.get((int)Point.y, (int)Point.x);
for (String string : set){
cvRGB = CvColor.get(string);
if (colorMiss(RGB, cvRGB, r, g, b)){
return string;
}
}
// 若颜色识别依然失败 增加容忍度
while (true){
for (String string : set){
cvRGB = CvColor.get(string);
if (colorMiss(RGB, cvRGB, r, g, b)){
return string;
}
}
r += 5;
g += 5;
b += 5;
}
}
}
识别颜色用到的方法:
得到图形的中心点坐标。
根据RGB设置颜色。
这里我设置的颜色比较少,都是从实际图片上取的值,若有需要自行添加就好。
/**
* 取得中心坐标
*/
public Point getCenterPoint(Point[] points) {
double centerX = 0.0;
double centerY = 0.0;
for (Point point : points) {
centerX += point.x;
centerY += point.y;
}
Point result = new Point();
result.x = (int) (centerX / points.length);
result.y = (int) (centerY / points.length);
return result;
}
/**
* 判断颜色
*/
public boolean colorMiss(double[]a, double[]b, int x, int y, int z){
if (Math.abs(a[0] - b[0]) <= x
&& Math.abs(a[1] - b[1]) <= y
&& Math.abs(a[2] - b[2]) <= z){
return true;
}else return false;
}
/**
* 颜色 RGB
*/
public HashMap<String, double[]> CvColor(){
HashMap<String, double[]> map = new HashMap<String, double[]>();
map.put("红色", new double[]{180.0, 30.0, 50.0});
map.put("蓝色", new double[]{10.0, 140.0, 220.0});
map.put("黄色", new double[]{215.0, 225.00, 120.0});
map.put("品色", new double[]{190.0, 173.0, 220.0});
map.put("黑色", new double[]{10.0, 15.0, 10.0});
map.put("白色", new double[]{255.0, 255.0, 255.0});
return map;
}
因为要用到所以临时学的opencv,学的时间短,比较菜,希望对大家有所帮助,若有什么问题及时提出来。