纯java代码实现安卓的CV识别图形形状颜色
半年前开的帖,一直在忙 回来更新下。
cv环境已经没有了,图就用以前的吧,代码我贴出来,凭记忆解释一下,算法实现的,我会说明逻辑
首先,环境是OpenCV3.4.3,jdk1.8,安卓4.3,工具类是在java编辑和测试的,java稍改即可用,现在代码是从同学那拷贝过来的,已经用在了安卓环境,白色字体是反向绘制出来的,以下是代码 主要分三步,抓图预处理,形状判定,色彩判定。
先贴出简单封装的安卓调用CV工具类
import java.util.ArrayList;
import java.util.List;
import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import android.graphics.Bitmap;
import android.os.Environment;
import android.util.Log;
/**
* 重构 OpenCV Utils
*
* @author PiaoZhenJia
*
*/
public class OpenCvUtils {
private static final String tag = "CVUT2";
/**
* 得到图形中心点
*/
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 Point[] findp(Mat mat) {
final int maxCorners = 50, blockSize = 3;
final double qualityLevel = 0.01, minDistance = 20.0, k = 0.04;
final boolean userHarrisDetector = false;
MatOfPoint corners = new MatOfPoint();// 省略了转化成灰度图像
this.toGrayMat(mat);
// 计算角点
// image:8位或32位浮点型输入图像,单通道
// corners:保存检测出的角点
// maxCorners:角点数目最大值,如果实际检测的角点超过此值,则只返回前maxCorners个强角点
// qualityLevel:角点的品质因子---------重要
// minDistance:对于初选出的角点而言,如果在其周围minDistance范围内存在其他更强角点,则将此角点删除
// mask:指定感兴趣区,如不需在整幅图上寻找角点,则用此参数指定ROI。也可以new Mat()来代替,这样就是没有mask.
// blockSize:计算协方差矩阵时的窗口大小
// useHarrisDetector:指示是否使用Harris角点检测,如不指定,则计算shi-tomasi角点
// harrisK:Harris角点检测需要的k值
Imgproc.goodFeaturesToTrack(mat, corners, maxCorners, qualityLevel,
minDistance, new Mat(), blockSize, userHarrisDetector, k);
Point[] pCorners = corners.toArray();
return pCorners;
}
/**
* 画个圈圈祝福你
*/
public void drawCircleByPoint(Mat mat, Point point) {
Imgproc.circle(mat, point, 2, new Scalar(255, 255, 0), 1);
}
/**
* 横向填充杂色
*/
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;
}
}
}
Log.d(tag, "背景涂黑完成");
}
/**
* 从Bitmap得到Mat
*/
public Mat bitmapToMat(Bitmap bitmap) {
Bitmap bit = bitmap.copy(Bitmap.Config.ARGB_8888, false);
Mat src = new Mat(bit.getHeight(), bit.getWidth(), CvType.CV_8UC(3));
Utils.bitmapToMat(bit, src);
Log.d(tag, "Bitmap转换Mat完成");
return src;
}
/**
* 膨胀处理 参数 3,3,1
*/
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);
Log.d(tag, "膨胀完成");
}
/**
* 腐蚀处理 参数3,3,1
*/
public void toErode(Mat mat, int i, int j, int iterations) {
Imgproc.erode(mat, mat, Imgproc.getStructuringElement(
Imgproc.MORPH_RECT, new Size(i, j)), new Point(-1, -1),
iterations);
Log.d(tag, "腐蚀完成");
}
/**
* 复制Mat对象
*/
public Mat cloneMat(Mat mat) {
return mat.clone();
}
/**
* 生成纯色Mat对象
*/
public Mat makeBGRMat(int b, int g, int r) {
return new Mat(360, 640, CvType.CV_8UC3, new Scalar(b, g, r));
}
/**
* 查找轮廓并返回轮廓数组 最好传入阈值图
*/
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);
Log.d(tag, "找到轮廓" + contours.size() + "个");
return contours;
}
/**
* 画出轮廓 根据轮廓数组在一张图上画 需指定数组下标
*/
public void drawContoursToMat(Mat mat, List<MatOfPoint> contours,
int index, int b, int g, int r, int size) {
Imgproc.drawContours(mat, contours, index, new Scalar(b, g, r, 0), size);
Log.v(tag, "绘制第" + index + "个轮廓");
}
/**
* 二值化图片 参数i,j参考150,255
*/
public void toBinaryMat(Mat from, int i, int j) {
Imgproc.threshold(from, from, i, j, Imgproc.THRESH_BINARY);
Log.d(tag, "二值化完成");
}
/**
* 灰度化图像
*/
public void toGrayMat(Mat from) {
Imgproc.cvtColor(from, from, Imgproc.COLOR_BGR2GRAY);
Log.d(tag, "灰度化完成");
}
/**
* 图像颜色反转
*/
public void toReverseColorMat(Mat from) {
Core.bitwise_not(from, from);
Log.d(tag, "颜色反转完成");
}
/**
* 模糊图像,参数i,j,k参考5,5,0
*/
public void toGaussUnClearMat(Mat from, int i, int j, int k) {
Imgproc.GaussianBlur(from, from, new Size(i, j), k);
Log.d(tag, "高斯模糊完成");
}
/**
* 在图上写字
*/
public void printWordsOnMat(Mat mat, Point p, String text) {
p.x = p.x - 100;
Imgproc.putText(mat, text, p, 18, 0.5, new Scalar(255, 255, 255), 1);
}
/**
* 合并直线上的点[汇总]
*
* @param points
* @return
*/
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;
}
Log.d(tag, "数组变化:" + points.length + " -> " + np.length);
return np;
}
/**
* 合并直线上的点[分步]
*/
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;
}
}
/**
* 删除三点中处于中间的点
*/
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;
}
}
/**
* 通过描边点得出形状
*/
public String findShape(Point[] checkedPoints) {
int length = checkedPoints.length;
if (length < 3) {
return "ShapeError";
} else if (length == 3) {
return "SanJiao";
} else if (length == 5) {
return "WuJiao";
} else if (length > 5) {
return "YuanXing";
} 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);
Log.d(tag, String.format("四边形某顶点角度为%.2f", angelP));
if (angelP > 80 && angelP < 100) {// 直角
double dis1 = twoPointsDistance(checkedPoints[0], p[0]);
double dis2 = twoPointsDistance(checkedPoints[0], p[1]);
double distanceRatio = dis1 / dis2;
Log.d(tag, String.format("四边形临边长度差距比为%.2f", distanceRatio));
if (distanceRatio > 0.80 && distanceRatio < 1.20) {
return "ZhengFang";
} else {
return "ChangFang";
}
} else {
return "LingXing";
}
} 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)));
}
/**
* 将Mat对象保存到文件系统
*/
public void saveMatAsPngFile(Mat mat, String filename) {
Log.d(tag, "储存Mat");
try {
Imgcodecs.imwrite(Environment.getExternalStorageDirectory()
+ "/CV/" + filename + ".png", mat);
Log.i(tag, "Mat存储完成");
} catch (Exception e) {
Log.e(tag, "Mat存储出错");
}
}
/**
* 将Mat对象保存到文件系统
*/
public void saveMatAsPngFileAndTimestamp(Mat mat, String filename) {
Log.d(tag, "储存Mat");
try {
Imgcodecs.imwrite(Environment.getExternalStorageDirectory()
+ "/CV/" + filename + System.currentTimeMillis() + ".jpg",
mat);
Log.i(tag, "Mat存储完成");
} catch (Exception e) {
Log.e(tag, "Mat存储出错");
}
}
// private static final int[] RED = { 255, 0, 0 };
// private static final int[] GREEN = { 0, 255, 0 };
// private static final int[] BLUE = { 0, 0, 255 };
// private static final int[] YELLOW = { 255, 255, 0 };
// private static final int[] PIN = { 255, 0, 255 };
// private static final int[] QING = { 0, 255, 255 };
// private static final int[] BLACK = { 0, 0, 0 };
// private static final int[] WHITE = { 255, 255, 255 };
//
private static final int[] RED = { 255, 0, 0 };
private static final int[] GREEN = { 0, 255, 0 };
private static final int[] BLUE = { 0, 0, 255 };
private static final int[] YELLOW = { 255, 255, 0 };
private static final int[] PIN = { 255, 0, 255 };
private static final int[] QING = { 0, 255, 255 };
private static final int[] BLACK = { 0, 0, 0 };
private static final int[] WHITE = { 255, 255, 255 };
private static ArrayList<int[]> COLORS = new ArrayList<>();
static {
COLORS.add(RED);
COLORS.add(GREEN);
COLORS.add(BLUE);
COLORS.add(YELLOW);
COLORS.add(PIN);
COLORS.add(QING);
COLORS.add(BLACK);
COLORS.add(WHITE);
}
/**
* 确定形状的颜色
*/
public String findColor(Mat colorfulMat, Point[] checkedPoints) {
Point centerPoint = getCenterPoint(checkedPoints);
double[] colorBGR = colorfulMat.get((int) centerPoint.y,
(int) centerPoint.x);
double maxlightR = 0;// 确定亮度增大系数
double maxlightG = 0;
double maxlightB = 0;
for (int y = 0; y < colorfulMat.height(); y++) {// 遍历图片 找到最亮的点做参考
for (int x = 0; x < colorfulMat.width(); x++) {
double pixle[] = colorfulMat.get(y, x);
if (pixle[0] > maxlightB)
maxlightB = pixle[0];
if (pixle[1] > maxlightG)
maxlightG = pixle[1];
if (pixle[2] > maxlightR)
maxlightR = pixle[2];
}
}// 用于去除亮度和色调影响
maxlightR = 255 / maxlightR;
maxlightG = 255 / maxlightG;
maxlightB = 255 / maxlightB;
int[] colorRGB = { (int) (colorBGR[2] * maxlightR),
(int) (colorBGR[1] * maxlightG),
(int) (colorBGR[0] * maxlightB) };
int[] missNmuber = new int[8];
for (int i = 0; i < 8; i++) {
missNmuber[i] = colorMiss(colorRGB, COLORS.get(i));
}
int minIndex = 0;
int minNumb = 999;
for (int i = 0; i < missNmuber.length; i++) {
if (missNmuber[i] < minNumb) {
minNumb = missNmuber[i];
minIndex = i;
}
}
Log.d(tag, String.format(
"R:%d, G:%d, B:%d, light:[%.2f * %.2f * %.2f] => ",
colorRGB[0], colorRGB[1], colorRGB[2], maxlightR, maxlightG,
maxlightB));
switch (minIndex) {
case 0:
return "Hong";
case 1:
return "Lv";
case 2:
return "Lan";
case 3:
return "Huang";
case 4:
return "Pin";
case 5:
return "Qing";
case 6:
return "Hei";
case 7:
return "Bai";
default:
return "ColorError";
}
}
/**
* 计算颜色差距
*/
private int colorMiss(int[] c1, int[] c2) {
return Math.abs(c1[0] - c2[2]) + Math.abs(c1[1] - c2[1])
+ Math.abs(c1[2] - c2[0]);
}
/**
* 从文件系统图片读取Mat对象
*/
public Mat getImageAsFile(String filename) {// FIXME
Log.d(tag, "读取Mat");
Mat image = Imgcodecs.imread(Environment.getExternalStorageDirectory()
+ "/CV/" + filename);
Log.i(tag, "Mat读取完成");
return image;
}
}
以下是调用过程
public void colorAndShape() {// XXX opencv图形识别
timer = new Timer();
timer.schedule(new TimerTask() {
OpenCvUtils cvut = new OpenCvUtils();
ArrayList<ColorShapeInfo> resultList = new ArrayList<>();
int erzhi = 130;
@Override
public void run() {//说明:主要的调用在这里哈,线程那块可以忽略不计
Log.d("pzj", "in to cv !!");
// new FileService().savePhoto(bitmap, "CV/doit.png");
// Mat image = cvut.getImageAsFile("CV/doit.png");
Mat image = cvut.bitmapToMat(bitmap);
Mat m1 = cvut.cloneMat(image);
Mat declarMat = cvut.cloneMat(image);
cvut.toReverseColorMat(image);// 反色
cvut.toGaussUnClearMat(declarMat, 9, 9, 9);// 模糊图像,用于推测颜色
Mat m2 = cvut.cloneMat(declarMat);
Mat srcWrite = cvut.cloneMat(declarMat);
cvut.toDilate(image, 1, 1, 1);// 膨胀
cvut.toGrayMat(image);// 灰度化
Mat binary = cvut.cloneMat(image);
cvut.toBinaryMat(binary, erzhi, 255);// 二值化
cvut.coverBackGroundToBlack(binary);// 背景变黑
cvut.saveMatAsPngFileAndTimestamp(binary, "binary");// SAVE
List<MatOfPoint> contoursList = cvut.findContoursList(binary);// 找到轮廓
for (int i = 0; i < contoursList.size()
&& contoursList.size() <= 9; i++) {// 处理单个轮廓
Mat mat = cvut.makeBGRMat(0, 0, 0);// 黑色背景
cvut.drawContoursToMat(mat, contoursList, i, 0, 255, 0, 1);// 白色描边
Point[] points = cvut.findp(mat);// 找到描边点
Point[] checkedPoints = cvut.checkPoint(points);// 清除同一条直线上的点
//ColorShapeInfo csi = ;
//String shape = cvut.findShape(checkedPoints);// 确定形状
//String color = cvut.findColor(declarMat, checkedPoints);// 确定颜色
resultList.add(new ColorShapeInfo(cvut.findColor(declarMat, checkedPoints), cvut.findShape(checkedPoints)));
for (int j = 0; j < checkedPoints.length; j++) {
cvut.drawCircleByPoint(mat, checkedPoints[j]);
}
cvut.saveMatAsPngFile(mat, "Shape" + i);
}
if (resultList.size() != 0) {
Log.d("CvResoult", Arrays.toString(resultList.toArray()));
cvut.saveMatAsPngFileAndTimestamp(m2, "declar");// SAVE
cvut.saveMatAsPngFileAndTimestamp(m1, "image");// SAVE
timer.cancel();
this.cancel();
} else {
resultList.clear();
if (erzhi <= 180) {
erzhi += 5;
} else {
erzhi = 150;
}
}
}
}, 0, 200);
}
好吧,原谅我时间太久说不清细节了,我在这里简单讲一下逻辑,剩下的有需要的小伙伴可以对着我的代码和注释看,我的命名规范和注释还是有一定可读性的。
我又翻出了两张当时发的朋友圈,结合这两张图看一下,其实他们说明了我主要的处理过程。
在这里我采用自问自答的方式
1.“横向填充杂色”这个方法是用来做什么的:
这个方法适用于我当时做的东西,如图可见,这张图片在经过二值化处理后,是这个样子的
那么opencv在不使用分类器的情况下,很难识别这张图片,当时时间紧张,都是算法实现,没空学python,没时间搞c的svm,可见外边一圈是白的,中间是个黑框框,黑框框里的东西才是我们想要的结果,顾想到了填充白色部分,他的原理是 从两边向中间扫描,白色就变成黑色,如果遇到了黑色,停止,去搞下一行。
好的,那么现在,经过了二值化(如果需要的话你可能会同时使用膨胀和腐蚀方法,详见别人的贴子)我们得到了这样的图,才好处理
**2.**前面的反色方法做什么的 为什么一定要反色
反色其实在我的代码里最终体现为黑色变成了白色的交换,由经验可得,cv只去提取白色轮廓,并不提取黑色轮廓,如果上面这张图片黑白交换,是得不到正确结果的,原理不知,你可以探究一下。根据你的具体情况,判断你要不要也使用反色。
**3.**如何在这张图中提取形状
形状提取出来是这样的,没有白色的小圆圈,白色小圆圈是调用“画个圈圈”那个方法绘制的
代码在上面是这样调用的:
List contoursList = cvut.findContoursList(binary);// 找到轮廓
好,轮廓提取出来,我又将这个轮廓绘制在了一张等大小的空白图上(其实是黑色,空黑),截图是三张图拼在了一起,并不是这样
如上图只是为了展示效果,单独生成出来的,处理的时候每个轮廓分别存了一张图。我代码里有写的,重新把单个轮廓绘制在一张黑图上。
好,轮廓提取了出来,现在是黑纸白边,接下来的工作是找到顶点。上面有写,调用了这句:
Point[] points = cvut.findp(mat);// 找到描边点
我简单的画一下,参数要慢慢调,调完以后是这种情况比较理想
这张图是我用截屏手工画的,对付看吧,顶点有点多,稍微少一点才是比较理想的状态。
当然,如果你只识别单个形状的话,大可调参数成cv只绘制四个顶点。
在我这个项目里,我需要过滤这些点,只留下顶点。参数慢慢调,上面有,我再贴一下
其实这些汉字的原版是另一个大神写的,也在csdn,我在他的基础上补充了一些自己的理解。时间久了 原谅我不找出处了,正在研究这块的小伙伴如果刚好看到,可以告诉我,我补上链接。如我所说,最重要的参数是角点品质因子。
好,那么如何取出顶点,我解释一下我上面的算法:三层循环,这个算法可以优化一多半,我知道,没做,爱咋咋地。每层循环都是逐个取点,比如取出1,2,3这三个点,然后取1,2,4然后1,2,5…以此类推,如果1,2,5这三个点在一条直线上,那么删除中间那个点,只留两边的点,这里又有两个问题:
**3.(1)**怎么判断在一条直线上:
计算斜率,没记错的话是x1/y1:x2/y2,求k值嘛,大家初中都学过,后来发现斜率并不好用,因为斜率不是线性的,靠近水平变化很小,靠近垂直变化很大(没记错的话)于是使用了java的求角度函数,就是k值转角度吧好像。如果1-2和1-5折两条直线的水平夹角非常接近,而他们又有共同点(就是1这个点呗)那我就认为这三个点在一条直线上,示意图:
于是我选择删除②点,因为最后要通过顶点数判断形状。
**3.(2)**如何知道2点在中间:
这个就比较简单了,你可以写个简单实现,将他们三个的x坐标排序,谁在中间就把谁变成null。
好,那么经过了一番遍历,数组中剩下了三角形的三个顶点,或者正方形的四个顶点
**4.**怎么证明三角形是三角形,正方形是正方形呢:
参见工具类中findShape方法,其实事情发展到今天,三角形就很好判断了,有三个顶点的图形就是三角形,四边形稍显复杂,因为他分正方形,长方形,菱形,平行四边形。而当时不需要判断平行四边形,所以我只介绍下如何区分前三种,这个搞懂了,平行四边形就不难了吧。首先判断了顶点的角度,这里引申出了一个问题,如何判断顶点角度:
**4.(1)**如何判断顶点角度:
在四边形里,找到一个点,随便找,然后求距离 |x1-x2|+|y1-y2| 虽然这不是求距离公式,但是距离越小这个值是不是就应该越小。所以四边形另外三个顶点中,距离更近的两个点就是这个点临边上的顶点,根据上面那张1-2 1-5那个很丑的手绘图所讲的故事,我们很容易通过java.math得出这个角度。
好,得出了顶点角度,我们可以大致判断,(因为我是拍照的图,如果是截屏那种,就不必那么“大致”)如果这个角度在85°-95°之间,那么这是一个直角四边形,如果不在这个范围,铁定是菱形。好,现在分开了菱形,那么正方形和长方形也好判断,之前不是已经求出了某个点的两个临近点,再计算一下距离,如果相差很小 a>0.9b && a<1.1b 比如这样,就是判断临边是否一样长,一样长的就是正方形呗。
**5.**如何判断图形颜色呢:
我们现在已经提取出来了图形,并且知道他所有的故事,唯独不知道颜色,好办,我们把他的每个顶点取出来,xy坐标取平均值 (x1+x2+x3+x4+x5)/5 这就是他的x轴中心点,再取出y轴中心点,就得到了图片中心点(好像不是严谨的)我们取出这个点的RGB,与基色对比,看跟谁接近。
**5.(1)**如何对比:
代码里有,我简单用三原色说明,红色是255,0,0绿色是0,255,0蓝色是0,0,255。假如你的图形只可能是这三种颜色,并且取出来了中心点,RGB的值为201,29,31。通过目测,我们很容易得到这是一个红色。如何计算呢,将每个可能的颜色分别和该图RGB取差值,差值越小,就越接近,比如|255-201|+|0-29|+|0-31|这个运算的结果,就是该图和红色的距离,这个距离越小,他就越接近红色。你用该图中心点与红绿蓝分别取出距离,会发现他离红色距离是最小的。
**5.(2)**颜色识别效果差:
如果你是我这种相机图的识别,而不是截屏图的识别,很容易遇到这种问题。打开PS,仔细分析图像数据,你会发现,本来应该是255,0,0的大红色,实际上R的值也就180+到头了,那么你需要参考这段代码:
上面都有帖出来的哈,这段代码的大概意思是,遍历整张图,找到最红的红色点,比如是185,这说明本图的红色还不够红,本应是255,怎么就185呢,这是相机拍的色差,我对此作了弥补,尝试提高精度。对三原色每种颜色都进行一次审查(全图遍历),如发现最红的点R值是185,最绿的点G值是190,最蓝的点B值是225,那么我在计算5.(1)中颜色距离的时候,最好就加上一个系数。所有点的R值都乘以1.37(就是255/185),G值都乘以1.34(255/190),B值也是如此,乘上1.13 。可以一定程度上,弥补因为相机问题导致的色彩不够饱和的问题。这样结果更容易接近预期。
最后通过反向绘制,将拼音画到了原图上。
→→→→→题外话分割线→→→→→
当初搞这个东西废了好大的力气,这方面资源确实不太丰富,其中包括工具类中很多的方法的原型,都是翻的csdn中大神的贴子,时间久了,非常愧疚没将他们的链接贴上,虽然已经改的面目全非,还是非常感谢你们无私的分享,帮助了当时对cv一窍不通的我,也希望我写的这乱糟一团对他人有所帮助。