文章目录
-
- 一、OpenCV在Android中的集成与配置
-
- [1.1 OpenCV简介](#1.1 OpenCV简介)
- [1.2 在Android Studio中集成OpenCV](#1.2 在Android Studio中集成OpenCV)
-
- [1.2.1 通过Gradle依赖集成](#1.2.1 通过Gradle依赖集成)
- [1.2.2 通过模块方式集成](#1.2.2 通过模块方式集成)
- [1.2.3 初始化OpenCV](#1.2.3 初始化OpenCV)
- [1.3 OpenCV基础类介绍](#1.3 OpenCV基础类介绍)
- 二、指定区域图像抓取与对比
-
- [2.1 图像抓取基础](#2.1 图像抓取基础)
- [2.2 指定区域图像抓取实现](#2.2 指定区域图像抓取实现)
-
- [2.2.1 从Bitmap中截取指定区域](#2.2.1 从Bitmap中截取指定区域)
- [2.2.2 使用OpenCV截取指定区域](#2.2.2 使用OpenCV截取指定区域)
- [2.3 图像对比技术](#2.3 图像对比技术)
-
- [2.3.1 均方误差(MSE)对比](#2.3.1 均方误差(MSE)对比)
- [2.3.2 结构相似性(SSIM)对比](#2.3.2 结构相似性(SSIM)对比)
- [2.3.3 特征点匹配对比](#2.3.3 特征点匹配对比)
- [2.4 图像对比应用实例](#2.4 图像对比应用实例)
-
- [2.4.1 图像相似度检测](#2.4.1 图像相似度检测)
- [2.4.2 图像差异可视化](#2.4.2 图像差异可视化)
- 三、指定点颜色提取与对比
-
- [3.1 颜色空间基础](#3.1 颜色空间基础)
- [3.2 指定点颜色提取](#3.2 指定点颜色提取)
-
- [3.2.1 从Bitmap中获取像素颜色](#3.2.1 从Bitmap中获取像素颜色)
- [3.2.2 使用OpenCV获取像素颜色](#3.2.2 使用OpenCV获取像素颜色)
- [3.3 颜色空间转换](#3.3 颜色空间转换)
- [3.4 颜色对比技术](#3.4 颜色对比技术)
-
- [3.4.1 欧氏距离颜色对比](#3.4.1 欧氏距离颜色对比)
- [3.4.2 CIEDE2000颜色差异算法](#3.4.2 CIEDE2000颜色差异算法)
- [3.4.3 颜色相似度判断](#3.4.3 颜色相似度判断)
- [3.5 颜色对比应用实例](#3.5 颜色对比应用实例)
-
- [3.5.1 屏幕取色器实现](#3.5.1 屏幕取色器实现)
- [3.5.2 颜色匹配检测](#3.5.2 颜色匹配检测)
- 四、指定区域OCR内容提取
-
- [4.1 OCR技术简介](#4.1 OCR技术简介)
- [4.2 Tesseract OCR集成](#4.2 Tesseract OCR集成)
-
- [4.2.1 添加Tesseract依赖](#4.2.1 添加Tesseract依赖)
- [4.2.2 初始化Tesseract](#4.2.2 初始化Tesseract)
- [4.3 图像预处理优化OCR结果](#4.3 图像预处理优化OCR结果)
-
- [4.3.1 基本预处理流程](#4.3.1 基本预处理流程)
- [4.3.2 高级预处理技术](#4.3.2 高级预处理技术)
- [4.4 指定区域OCR实现](#4.4 指定区域OCR实现)
-
- [4.4.1 从指定区域提取文本](#4.4.1 从指定区域提取文本)
一、OpenCV在Android中的集成与配置
1.1 OpenCV简介
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习软件库,包含超过2500种优化算法,涵盖图像处理、模式识别、机器学习等众多领域。在Android平台上,OpenCV为开发者提供了强大的图像处理能力,可以用于实现各种复杂的计算机视觉应用。
1.2 在Android Studio中集成OpenCV
1.2.1 通过Gradle依赖集成
最简单的方式是通过Gradle添加OpenCV依赖:
gradle
dependencies {
implementation 'org.opencv:opencv-android:4.5.5'
}
1.2.2 通过模块方式集成
更灵活的方式是下载OpenCV Android SDK并将其作为模块导入:
- 从OpenCV官网下载Android SDK
- 在Android Studio中选择File -> New -> Import Module
- 选择OpenCV SDK中的java文件夹
- 修改模块的build.gradle文件,确保与主项目兼容
1.2.3 初始化OpenCV
在应用启动时需要初始化OpenCV库:
java
public class MainActivity extends AppCompatActivity {
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
if (status == LoaderCallbackInterface.SUCCESS) {
Log.i("OpenCV", "OpenCV loaded successfully");
} else {
super.onManagerConnected(status);
}
}
};
@Override
protected void onResume() {
super.onResume();
if (!OpenCVLoader.initDebug()) {
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, mLoaderCallback);
} else {
mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
}
}
}
1.3 OpenCV基础类介绍
在Android中使用OpenCV主要涉及以下几个核心类:
- Mat:OpenCV的基础矩阵类,用于存储图像数据
- Bitmap:Android的位图类,需要与Mat相互转换
- Imgproc:图像处理类,包含各种图像处理算法
- Core:核心功能类,提供基本数学运算和矩阵操作
- Feature2d:特征检测与描述类
二、指定区域图像抓取与对比
2.1 图像抓取基础
在Android中获取图像主要有以下几种方式:
- 从相机捕获
- 从图库选择
- 从资源文件加载
- 从网络下载
2.2 指定区域图像抓取实现
2.2.1 从Bitmap中截取指定区域
java
public static Bitmap cropBitmap(Bitmap srcBitmap, Rect region) {
if (srcBitmap == null || region == null) return null;
try {
// 确保区域在图像范围内
int x = Math.max(0, region.left);
int y = Math.max(0, region.top);
int width = Math.min(srcBitmap.getWidth() - x, region.width());
int height = Math.min(srcBitmap.getHeight() - y, region.height());
return Bitmap.createBitmap(srcBitmap, x, y, width, height);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
2.2.2 使用OpenCV截取指定区域
java
public static Mat cropImage(Mat srcMat, Rect roi) {
if (srcMat.empty() || roi == null) return null;
try {
// 确保ROI在图像范围内
Rect adjustedRoi = new Rect(
Math.max(0, roi.x),
Math.max(0, roi.y),
Math.min(srcMat.cols() - roi.x, roi.width),
Math.min(srcMat.rows() - roi.y, roi.height)
);
return new Mat(srcMat, adjustedRoi);
} catch (Exception e) {
e.printStackTrace();
return new Mat();
}
}
2.3 图像对比技术
2.3.1 均方误差(MSE)对比
java
public static double compareImagesMSE(Mat img1, Mat img2) {
if (img1.rows() != img2.rows() || img1.cols() != img2.cols()) {
throw new IllegalArgumentException("Images must have same dimensions");
}
Mat diff = new Mat();
Core.absdiff(img1, img2, diff);
diff.convertTo(diff, CvType.CV_32F);
diff = diff.mul(diff);
Scalar mse = Core.mean(diff);
return (mse.val[0] + mse.val[1] + mse.val[2]) / 3;
}
2.3.2 结构相似性(SSIM)对比
java
public static double compareImagesSSIM(Mat img1, Mat img2) {
// 转换为灰度图像
Mat gray1 = new Mat();
Mat gray2 = new Mat();
Imgproc.cvtColor(img1, gray1, Imgproc.COLOR_BGR2GRAY);
Imgproc.cvtColor(img2, gray2, Imgproc.COLOR_BGR2GRAY);
// 参数设置
final double C1 = 6.5025, C2 = 58.5225;
int d = CvType.CV_32F;
Mat I1 = new Mat(), I2 = new Mat();
gray1.convertTo(I1, d);
gray2.convertTo(I2, d);
Mat I1_2 = I1.mul(I1);
Mat I2_2 = I2.mul(I2);
Mat I1_I2 = I1.mul(I2);
// 计算均值
Mat mu1 = new Mat(), mu2 = new Mat();
Imgproc.GaussianBlur(I1, mu1, new Size(11, 11), 1.5);
Imgproc.GaussianBlur(I2, mu2, new Size(11, 11), 1.5);
Mat mu1_2 = mu1.mul(mu1);
Mat mu2_2 = mu2.mul(mu2);
Mat mu1_mu2 = mu1.mul(mu2);
// 计算方差
Mat sigma1_2 = new Mat(), sigma2_2 = new Mat(), sigma12 = new Mat();
Imgproc.GaussianBlur(I1_2, sigma1_2, new Size(11, 11), 1.5);
Core.subtract(sigma1_2, mu1_2, sigma1_2);
Imgproc.GaussianBlur(I2_2, sigma2_2, new Size(11, 11), 1.5);
Core.subtract(sigma2_2, mu2_2, sigma2_2);
Imgproc.GaussianBlur(I1_I2, sigma12, new Size(11, 11), 1.5);
Core.subtract(sigma12, mu1_mu2, sigma12);
// 计算SSIM
Mat t1 = new Mat(), t2 = new Mat(), t3 = new Mat();
Core.multiply(mu1_mu2, new Scalar(2), t1);
Core.add(t1, new Scalar(C1), t1);
Core.multiply(sigma12, new Scalar(2), t2);
Core.add(t2, new Scalar(C2), t2);
Core.add(mu1_2, mu2_2, t3);
Core.add(t3, new Scalar(C1), t3);
Mat ssim_map = new Mat();
Core.multiply(t1, t2, ssim_map);
Core.divide(ssim_map, t3, ssim_map);
Scalar mssim = Core.mean(ssim_map);
return mssim.val[0];
}
2.3.3 特征点匹配对比
java
public static double compareImagesFeatureMatching(Mat img1, Mat img2) {
// 转换为灰度图像
Mat gray1 = new Mat();
Mat gray2 = new Mat();
Imgproc.cvtColor(img1, gray1, Imgproc.COLOR_BGR2GRAY);
Imgproc.cvtColor(img2, gray2, Imgproc.COLOR_BGR2GRAY);
// 检测ORB特征点
ORB orb = ORB.create();
MatOfKeyPoint keypoints1 = new MatOfKeyPoint();
MatOfKeyPoint keypoints2 = new MatOfKeyPoint();
Mat descriptors1 = new Mat();
Mat descriptors2 = new Mat();
orb.detectAndCompute(gray1, new Mat(), keypoints1, descriptors1);
orb.detectAndCompute(gray2, new Mat(), keypoints2, descriptors2);
// 使用BFMatcher进行匹配
BFMatcher matcher = BFMatcher.create(BFMatcher.BRUTEFORCE_HAMMING);
MatOfDMatch matches = new MatOfDMatch();
matcher.match(descriptors1, descriptors2, matches);
// 计算匹配质量
List<DMatch> matchesList = matches.toList();
double maxDist = 0;
double minDist = 100;
for (DMatch match : matchesList) {
double dist = match.distance;
if (dist < minDist) minDist = dist;
if (dist > maxDist) maxDist = dist;
}
// 筛选好的匹配点
List<DMatch> goodMatches = new ArrayList<>();
for (DMatch match : matchesList) {
if (match.distance <= Math.max(2 * minDist, 30.0)) {
goodMatches.add(match);
}
}
// 计算匹配率
double matchRatio = (double)goodMatches.size() / matchesList.size();
return matchRatio;
}
2.4 图像对比应用实例
2.4.1 图像相似度检测
java
public class ImageComparator {
private static final double MSE_THRESHOLD = 1000;
private static final double SSIM_THRESHOLD = 0.8;
private static final double FEATURE_MATCH_THRESHOLD = 0.5;
public enum ComparisonResult {
VERY_SIMILAR,
SIMILAR,
DIFFERENT,
INVALID
}
public static ComparisonResult compareImages(Bitmap bmp1, Bitmap bmp2, Rect roi1, Rect roi2) {
try {
// 转换Bitmap为Mat
Mat mat1 = new Mat();
Mat mat2 = new Mat();
Utils.bitmapToMat(bmp1, mat1);
Utils.bitmapToMat(bmp2, mat2);
// 裁剪指定区域
Mat cropped1 = cropImage(mat1, roi1);
Mat cropped2 = cropImage(mat2, roi2);
if (cropped1.empty() || cropped2.empty()) {
return ComparisonResult.INVALID;
}
// 调整大小一致
if (cropped1.size().width != cropped2.size().width ||
cropped1.size().height != cropped2.size().height) {
Imgproc.resize(cropped2, cropped2, cropped1.size());
}
// 计算各种相似度指标
double mse = compareImagesMSE(cropped1, cropped2);
double ssim = compareImagesSSIM(cropped1, cropped2);
double featureMatch = compareImagesFeatureMatching(cropped1, cropped2);
// 综合判断
if (mse < MSE_THRESHOLD && ssim > SSIM_THRESHOLD && featureMatch > FEATURE_MATCH_THRESHOLD) {
return ComparisonResult.VERY_SIMILAR;
} else if (ssim > SSIM_THRESHOLD || featureMatch > FEATURE_MATCH_THRESHOLD) {
return ComparisonResult.SIMILAR;
} else {
return ComparisonResult.DIFFERENT;
}
} catch (Exception e) {
e.printStackTrace();
return ComparisonResult.INVALID;
}
}
}
2.4.2 图像差异可视化
java
public static Bitmap visualizeImageDifference(Bitmap bmp1, Bitmap bmp2) {
try {
// 转换Bitmap为Mat
Mat mat1 = new Mat();
Mat mat2 = new Mat();
Utils.bitmapToMat(bmp1, mat1);
Utils.bitmapToMat(bmp2, mat2);
// 确保图像大小一致
if (mat1.size().width != mat2.size().width ||
mat1.size().height != mat2.size().height) {
Imgproc.resize(mat2, mat2, mat1.size());
}
// 计算差异
Mat diff = new Mat();
Core.absdiff(mat1, mat2, diff);
// 增强差异可视化
Core.normalize(diff, diff, 0, 255, Core.NORM_MINMAX);
Imgproc.cvtColor(diff, diff, Imgproc.COLOR_BGR2RGB);
// 转换回Bitmap
Bitmap result = Bitmap.createBitmap(diff.cols(), diff.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(diff, result);
return result;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
三、指定点颜色提取与对比
3.1 颜色空间基础
OpenCV支持多种颜色空间,常用的有:
- RGB/BGR:默认颜色空间
- HSV/HSL:色调、饱和度、亮度/明度
- Lab:感知均匀的颜色空间
- YCrCb:亮度和色度分量
- Grayscale:灰度图像
3.2 指定点颜色提取
3.2.1 从Bitmap中获取像素颜色
java
public static int getPixelColor(Bitmap bitmap, int x, int y) {
if (bitmap == null || x < 0 || y < 0 || x >= bitmap.getWidth() || y >= bitmap.getHeight()) {
return 0;
}
return bitmap.getPixel(x, y);
}
public static int[] getPixelColorARGB(Bitmap bitmap, int x, int y) {
int pixel = getPixelColor(bitmap, x, y);
return new int[] {
(pixel >> 24) & 0xff, // Alpha
(pixel >> 16) & 0xff, // Red
(pixel >> 8) & 0xff, // Green
pixel & 0xff // Blue
};
}
3.2.2 使用OpenCV获取像素颜色
java
public static double[] getPixelColor(Mat mat, int x, int y) {
if (mat.empty() || x < 0 || y < 0 || x >= mat.cols() || y >= mat.rows()) {
return new double[]{0, 0, 0};
}
double[] pixel = mat.get(y, x); // OpenCV中使用(row, col)顺序
return pixel;
}
public static Scalar getPixelColorScalar(Mat mat, int x, int y) {
double[] pixel = getPixelColor(mat, x, y);
if (pixel == null || pixel.length < 3) {
return new Scalar(0, 0, 0);
}
return new Scalar(pixel[0], pixel[1], pixel[2]);
}
3.3 颜色空间转换
java
public static Mat convertColorSpace(Mat src, int conversionCode) {
if (src.empty()) return new Mat();
Mat dst = new Mat();
try {
Imgproc.cvtColor(src, dst, conversionCode);
} catch (Exception e) {
e.printStackTrace();
}
return dst;
}
// 常用颜色空间转换代码
public static final int COLOR_BGR2RGB = Imgproc.COLOR_BGR2RGB;
public static final int COLOR_BGR2HSV = Imgproc.COLOR_BGR2HSV;
public static final int COLOR_BGR2Lab = Imgproc.COLOR_BGR2Lab;
public static final int COLOR_BGR2YCrCb = Imgproc.COLOR_BGR2YCrCb;
public static final int COLOR_BGR2GRAY = Imgproc.COLOR_BGR2GRAY;
3.4 颜色对比技术
3.4.1 欧氏距离颜色对比
java
public static double colorDistanceEuclidean(Scalar color1, Scalar color2) {
double diffR = color1.val[0] - color2.val[0];
double diffG = color1.val[1] - color2.val[1];
double diffB = color1.val[2] - color2.val[2];
return Math.sqrt(diffR * diffR + diffG * diffG + diffB * diffB);
}
3.4.2 CIEDE2000颜色差异算法
java
// 需要实现Lab颜色空间的Delta E计算
public static double colorDistanceCIEDE2000(Scalar lab1, Scalar lab2) {
// 简化的Delta E 2000计算
double L1 = lab1.val[0];
double a1 = lab1.val[1];
double b1 = lab1.val[2];
double L2 = lab2.val[0];
double a2 = lab2.val[1];
double b2 = lab2.val[2];
double deltaL = L2 - L1;
double meanL = (L1 + L2) / 2;
double C1 = Math.sqrt(a1 * a1 + b1 * b1);
double C2 = Math.sqrt(a2 * a2 + b2 * b2);
double meanC = (C1 + C2) / 2;
double G = 0.5 * (1 - Math.sqrt(Math.pow(meanC, 7) / (Math.pow(meanC, 7) + Math.pow(25, 7))));
double a1Prime = a1 * (1 + G);
double a2Prime = a2 * (1 + G);
double C1Prime = Math.sqrt(a1Prime * a1Prime + b1 * b1);
double C2Prime = Math.sqrt(a2Prime * a2Prime + b2 * b2);
double meanCPrime = (C1Prime + C2Prime) / 2;
double h1Prime = Math.toDegrees(Math.atan2(b1, a1Prime));
if (h1Prime < 0) h1Prime += 360;
double h2Prime = Math.toDegrees(Math.atan2(b2, a2Prime));
if (h2Prime < 0) h2Prime += 360;
double deltaHPrime;
if (Math.abs(h1Prime - h2Prime) <= 180) {
deltaHPrime = h2Prime - h1Prime;
} else if (h2Prime <= h1Prime) {
deltaHPrime = h2Prime - h1Prime + 360;
} else {
deltaHPrime = h2Prime - h1Prime - 360;
}
double deltaH = 2 * Math.sqrt(C1Prime * C2Prime) * Math.sin(Math.toRadians(deltaHPrime / 2));
double meanHPrime;
if (Math.abs(h1Prime - h2Prime) <= 180) {
meanHPrime = (h1Prime + h2Prime) / 2;
} else if (h1Prime + h2Prime < 360) {
meanHPrime = (h1Prime + h2Prime + 360) / 2;
} else {
meanHPrime = (h1Prime + h2Prime - 360) / 2;
}
double T = 1 - 0.17 * Math.cos(Math.toRadians(meanHPrime - 30))
+ 0.24 * Math.cos(Math.toRadians(2 * meanHPrime))
+ 0.32 * Math.cos(Math.toRadians(3 * meanHPrime + 6))
- 0.20 * Math.cos(Math.toRadians(4 * meanHPrime - 63));
double SL = 1 + (0.015 * Math.pow(meanL - 50, 2)) / Math.sqrt(20 + Math.pow(meanL - 50, 2));
double SC = 1 + 0.045 * meanCPrime;
double SH = 1 + 0.015 * meanCPrime * T;
double RT = -2 * Math.sqrt(Math.pow(meanCPrime, 7) / (Math.pow(meanCPrime, 7) + Math.pow(25, 7)))
* Math.sin(Math.toRadians(60 * Math.exp(-Math.pow((meanHPrime - 275) / 25, 2))));
double deltaE = Math.sqrt(Math.pow(deltaL / SL, 2)
+ Math.pow(deltaHPrime / SC, 2)
+ Math.pow(deltaH / SH, 2)
+ RT * (deltaCPrime / SC) * (deltaH / SH));
return deltaE;
}
3.4.3 颜色相似度判断
java
public static boolean areColorsSimilar(Scalar color1, Scalar color2, double threshold, int colorSpace) {
// 转换为指定颜色空间
Mat mat1 = new Mat(1, 1, CvType.CV_8UC3, color1);
Mat mat2 = new Mat(1, 1, CvType.CV_8UC3, color2);
if (colorSpace != Imgproc.COLOR_BGR2RGB) {
Imgproc.cvtColor(mat1, mat1, colorSpace);
Imgproc.cvtColor(mat2, mat2, colorSpace);
}
Scalar c1 = new Scalar(mat1.get(0, 0));
Scalar c2 = new Scalar(mat2.get(0, 0));
// 根据颜色空间选择合适的比较方法
if (colorSpace == Imgproc.COLOR_BGR2Lab) {
double deltaE = colorDistanceCIEDE2000(c1, c2);
return deltaE < threshold;
} else {
double distance = colorDistanceEuclidean(c1, c2);
return distance < threshold;
}
}
3.5 颜色对比应用实例
3.5.1 屏幕取色器实现
java
public class ColorPickerView extends View {
private Bitmap mBitmap;
private int mSelectedX = -1;
private int mSelectedY = -1;
private OnColorSelectedListener mListener;
public interface OnColorSelectedListener {
void onColorSelected(int color);
}
public ColorPickerView(Context context) {
super(context);
}
public ColorPickerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setBitmap(Bitmap bitmap) {
mBitmap = bitmap;
invalidate();
}
public void setOnColorSelectedListener(OnColorSelectedListener listener) {
mListener = listener;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mBitmap != null) {
// 绘制图像
Rect src = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
Rect dst = new Rect(0, 0, getWidth(), getHeight());
canvas.drawBitmap(mBitmap, src, dst, null);
// 绘制选择点标记
if (mSelectedX >= 0 && mSelectedY >= 0) {
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
canvas.drawCircle(mSelectedX, mSelectedY, 20, paint);
// 获取并显示颜色值
int color = getPixelColor(mBitmap, mSelectedX, mSelectedY);
paint.setStyle(Paint.Style.FILL);
paint.setColor(color);
canvas.drawRect(getWidth() - 100, 20, getWidth() - 20, 100, paint);
paint.setColor(Color.BLACK);
paint.setTextSize(30);
canvas.drawText(String.format("#%06X", (0xFFFFFF & color)),
getWidth() - 180, 70, paint);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mBitmap == null) return super.onTouchEvent(event);
int x = (int)(event.getX() * mBitmap.getWidth() / getWidth());
int y = (int)(event.getY() * mBitmap.getHeight() / getHeight());
if (x >= 0 && x < mBitmap.getWidth() && y >= 0 && y < mBitmap.getHeight()) {
mSelectedX = x;
mSelectedY = y;
if (mListener != null) {
int color = getPixelColor(mBitmap, x, y);
mListener.onColorSelected(color);
}
invalidate();
return true;
}
return super.onTouchEvent(event);
}
private int getPixelColor(Bitmap bitmap, int x, int y) {
if (x < 0 || y < 0 || x >= bitmap.getWidth() || y >= bitmap.getHeight()) {
return Color.TRANSPARENT;
}
return bitmap.getPixel(x, y);
}
}
3.5.2 颜色匹配检测
java
public class ColorMatchDetector {
private Scalar mTargetColor;
private double mThreshold;
private int mColorSpace;
public ColorMatchDetector(Scalar targetColor, double threshold, int colorSpace) {
mTargetColor = targetColor;
mThreshold = threshold;
mColorSpace = colorSpace;
}
public Mat findColorRegions(Mat inputImage) {
// 转换为目标颜色空间
Mat converted = new Mat();
Imgproc.cvtColor(inputImage, converted, mColorSpace);
// 创建目标颜色矩阵
Mat targetMat = new Mat(inputImage.size(), converted.type(), mTargetColor);
// 计算差异
Mat diff = new Mat();
Core.absdiff(converted, targetMat, diff);
// 分割通道
List<Mat> channels = new ArrayList<>();
Core.split(diff, channels);
// 计算总差异
Mat totalDiff = new Mat(channels.get(0).size(), CvType.CV_32F);
for (Mat channel : channels) {
Mat floatChannel = new Mat();
channel.convertTo(floatChannel, CvType.CV_32F);
Core.add(totalDiff, floatChannel.mul(floatChannel), totalDiff);
}
Core.sqrt(totalDiff, totalDiff);
// 创建掩码
Mat mask = new Mat();
Core.inRange(totalDiff, new Scalar(0), new Scalar(mThreshold), mask);
// 清理小区域
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(5, 5));
Imgproc.morphologyEx(mask, mask, Imgproc.MORPH_OPEN, kernel);
Imgproc.morphologyEx(mask, mask, Imgproc.MORPH_CLOSE, kernel);
return mask;
}
public List<Rect> findColorContours(Mat inputImage) {
Mat mask = findColorRegions(inputImage);
// 查找轮廓
List<MatOfPoint> contours = new ArrayList<>();
Mat hierarchy = new Mat();
Imgproc.findContours(mask, contours, hierarchy,
Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
// 转换为矩形
List<Rect> rects = new ArrayList<>();
for (MatOfPoint contour : contours) {
Rect rect = Imgproc.boundingRect(contour);
if (rect.area() > 100) { // 忽略小区域
rects.add(rect);
}
}
return rects;
}
}
四、指定区域OCR内容提取
4.1 OCR技术简介
OCR(Optical Character Recognition,光学字符识别)是将图像中的文字转换为可编辑文本的技术。在Android中实现OCR通常有以下几种方式:
- 使用Tesseract OCR引擎
- 使用Google ML Kit文本识别API
- 使用第三方OCR服务API
4.2 Tesseract OCR集成
4.2.1 添加Tesseract依赖
在build.gradle中添加:
gradle
dependencies {
implementation 'com.rmtheis:tess-two:9.1.0'
}
4.2.2 初始化Tesseract
java
public class TessOCR {
private TessBaseAPI mTess;
public TessOCR(Context context, String language) {
mTess = new TessBaseAPI();
// 训练数据路径
String datapath = context.getFilesDir() + "/tesseract/";
File dir = new File(datapath + "tessdata/");
if (!dir.exists()) {
dir.mkdirs();
}
// 检查训练数据文件是否存在
File tessdataFile = new File(datapath + "tessdata/" + language + ".traineddata");
if (!tessdataFile.exists()) {
try {
// 从assets复制训练数据
InputStream in = context.getAssets().open("tessdata/" + language + ".traineddata");
OutputStream out = new FileOutputStream(tessdataFile);
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
in.close();
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
mTess.init(datapath, language);
}
public String recognizeText(Bitmap bitmap) {
if (bitmap == null) return "";
mTess.setImage(bitmap);
return mTess.getUTF8Text();
}
public String recognizeText(Mat mat) {
if (mat.empty()) return "";
Bitmap bitmap = Bitmap.createBitmap(mat.cols(), mat.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(mat, bitmap);
return recognizeText(bitmap);
}
public void destroy() {
if (mTess != null) {
mTess.end();
}
}
}
4.3 图像预处理优化OCR结果
4.3.1 基本预处理流程
java
public static Mat prepareImageForOCR(Mat src) {
if (src.empty()) return new Mat();
Mat processed = new Mat();
// 1. 转换为灰度图像
Imgproc.cvtColor(src, processed, Imgproc.COLOR_BGR2GRAY);
// 2. 应用自适应阈值
Imgproc.adaptiveThreshold(processed, processed, 255,
Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,
Imgproc.THRESH_BINARY, 11, 2);
// 3. 降噪
Imgproc.medianBlur(processed, processed, 3);
// 4. 锐化
Mat kernel = new Mat(3, 3, CvType.CV_32F) {
{
put(0, 0, 0);
put(0, 1, -1);
put(0, 2, 0);
put(1, 0, -1);
put(1, 1, 5);
put(1, 2, -1);
put(2, 0, 0);
put(2, 1, -1);
put(2, 2, 0);
}
};
Imgproc.filter2D(processed, processed, -1, kernel);
return processed;
}
4.3.2 高级预处理技术
java
public static Mat advancedOCRPreprocessing(Mat src) {
if (src.empty()) return new Mat();
Mat processed = new Mat();
// 1. 转换为灰度图像
Imgproc.cvtColor(src, processed, Imgproc.COLOR_BGR2GRAY);
// 2. 应用CLAHE (对比度受限的自适应直方图均衡化)
CLAHE clahe = Imgproc.createCLAHE();
clahe.setClipLimit(2);
clahe.apply(processed, processed);
// 3. 非局部均值去噪
Photo.fastNlMeansDenoising(processed, processed, 10, 7, 21);
// 4. 边缘增强
Mat edges = new Mat();
Imgproc.Sobel(processed, edges, CvType.CV_8U, 1, 1);
Core.add(processed, edges, processed);
// 5. 局部自适应阈值
Imgproc.adaptiveThreshold(processed, processed, 255,
Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,
Imgproc.THRESH_BINARY, 15, 5);
// 6. 形态学操作去除小噪点
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(2, 2));
Imgproc.morphologyEx(processed, processed, Imgproc.MORPH_OPEN, kernel);
return processed;
}
4.4 指定区域OCR实现
4.4.1 从指定区域提取文本
java
public class RegionOCR {
private TessOCR mTessOCR;
public RegionOCR(Context context, String language) {
mTessOCR = new TessOCR(context, language);
}
public String extractTextFromRegion(Bitmap srcBitmap, Rect region) {
if (srcBitmap == null || region == null) return "";
// 裁剪指定区域
Bitmap cropped = Bitmap.createBitmap(srcBitmap,
region.left, region.top,
region.width(), region.height());
// 预处理图像
Mat mat = new Mat();
Utils.bitmapToMat(cropped, mat);
mat = prepareImageForOCR(mat);
// 执行OCR
return mTessOCR.recognizeText(mat);
}
public String extractTextFromRegion(Mat srcMat, Rect region) {
if (srcMat.empty() || region == null) return "";
// 调整区域确保在图像范围内
Rect adjusted = new Rect(
Math.max(0, region.x),
Math.max(0, region.y),
Math.min(srcMat.cols() - region.x, region.width),
Math.min(srcMat.rows() - region.y, region.height)
);
// 裁剪指定区域
Mat cropped = new Mat(srcMat, adjusted);
// 预处理图像
Mat processed = prepareImageForOCR(cropped);
// 执行OCR
return mTessOCR.recognizeText(processed);
}
public void destroy() {
if (mTessOCR != null) {
mTessOCR.destroy();
}
}
}