OpenCV Java 教程:从零开始构建图像识别应用 – wiki基地

OpenCV Java 教程:从零开始构建图像识别应用

引言

图像识别是计算机视觉领域的一个核心分支,它致力于让计算机理解和识别图像中的内容。OpenCV (Open Source Computer Vision Library) 是一个广泛使用的开源库,提供了丰富的图像处理和计算机视觉算法。本教程将引导你使用 OpenCV Java 接口,从零开始构建一个简单的图像识别应用程序。我们将涵盖 OpenCV 的安装配置、图像加载显示、基本图像处理操作,以及利用预训练模型实现对象识别等关键步骤。

一、OpenCV Java 环境搭建

在开始编写代码之前,我们需要先配置 OpenCV Java 开发环境。

1. 下载 OpenCV:

2. 解压 OpenCV:

  • 将下载的 OpenCV 压缩包解压到你选择的目录。例如:C:\opencv (Windows) 或 /opt/opencv (Linux/macOS)。

3. 配置系统环境变量 (可选但推荐):

  • 为了方便在命令行中使用 OpenCV,你可以配置系统环境变量。
  • Windows:
    • 打开“控制面板” -> “系统和安全” -> “系统” -> “高级系统设置”。
    • 点击“环境变量”按钮。
    • 在“系统变量”中,找到名为 “Path” 的变量,点击“编辑”。
    • 在变量值的末尾添加 OpenCV 的动态链接库 (DLL) 路径。通常是 C:\opencv\build\java\x64C:\opencv\build\java\x86 (取决于你的系统架构)。
  • Linux/macOS:
    • 打开 ~/.bashrc~/.zshrc 文件。
    • 添加以下行(替换成你的 OpenCV 安装路径):
      bash
      export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/opencv/build/lib
    • 运行 source ~/.bashrcsource ~/.zshrc 以应用更改。

4. 配置 Java 项目:

  • 在你的 Java 项目中,需要将 OpenCV 的 Java 库添加到构建路径中。

    • 使用 IDE (IntelliJ IDEA, Eclipse):
      • 右键点击项目,选择 “Build Path” -> “Configure Build Path” (Eclipse) 或 “Module Settings” -> “Dependencies” (IntelliJ IDEA)。
      • 点击 “Add JARs…” 或 “Add External JARs…”。
      • 导航到 OpenCV 解压目录下的 opencv\build\java 文件夹,选择 opencv-*.jar 文件。
    • 使用 Maven:

      • 在你的 pom.xml 文件中添加以下依赖:

      xml
      <dependency>
      <groupId>org.openpnp</groupId>
      <artifactId>opencv</artifactId>
      <version>${opencv.version}</version> <!-- 替换成你的 OpenCV 版本号 -->
      </dependency>

      你需要配置 opencv.version 属性。 可以在 opencv\build\java 文件夹中找到 jar 文件名来确定版本号。

5. 加载 OpenCV 本地库:

  • 在你的 Java 代码中,在使用 OpenCV 之前,需要加载 OpenCV 的本地库。通常,在 main 函数中添加以下代码:

“`java
import org.opencv.core.Core;

public class Main {
public static void main(String[] args) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
System.out.println(“OpenCV loaded successfully!”);
// … 你的代码 …
}
}
“`

如果一切配置正确,运行程序后,你应该看到 “OpenCV loaded successfully!” 的输出。

二、基本图像操作

配置好环境后,我们就可以开始进行图像处理了。

1. 加载图像:

使用 Imgcodecs.imread() 函数加载图像。

“`java
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;

public class ImageLoading {
public static void main(String[] args) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

    String imagePath = "path/to/your/image.jpg"; // 替换成你的图像路径
    Mat image = Imgcodecs.imread(imagePath);

    if (image.empty()) {
        System.out.println("Could not read the image: " + imagePath);
        return;
    }

    System.out.println("Image loaded successfully! Width: " + image.cols() + ", Height: " + image.rows());
}

}
“`

2. 显示图像:

OpenCV 本身没有提供直接在 Java 中显示图像的函数。我们需要借助第三方库,例如 Java Swing 或 JavaFX。 这里使用Java Swing为例:

“`java
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.core.Core;
import javax.swing.;
import java.awt.
;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;

public class ImageDisplay {

public static void main(String[] args) {
    System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

    String imagePath = "path/to/your/image.jpg"; // 替换成你的图像路径
    Mat image = Imgcodecs.imread(imagePath);

    if (image.empty()) {
        System.out.println("Could not read the image: " + imagePath);
        return;
    }

    ImageIcon icon = new ImageIcon(matToBufferedImage(image));
    JFrame frame = new JFrame();
    frame.setLayout(new FlowLayout());
    frame.setSize(image.cols() + 20, image.rows() + 50);
    JLabel label = new JLabel();
    label.setIcon(icon);
    frame.add(label);
    frame.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

public static BufferedImage matToBufferedImage(Mat matrix) {
    int type = BufferedImage.TYPE_BYTE_GRAY;
    if (matrix.channels() > 1) {
        type = BufferedImage.TYPE_3BYTE_BGR;
    }
    int bufferSize = matrix.channels() * matrix.cols() * matrix.rows();
    byte[] b = new byte[bufferSize];
    matrix.get(0, 0, b); // Get all the pixels
    BufferedImage image = new BufferedImage(matrix.cols(), matrix.rows(), type);
    final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
    System.arraycopy(b, 0, targetPixels, 0, b.length);
    return image;
}

}
“`

3. 图像类型转换:

使用 Imgproc.cvtColor() 函数进行颜色空间转换。例如,将彩色图像转换为灰度图像:

“`java
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.core.Core;

public class ColorConversion {
public static void main(String[] args) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

    String imagePath = "path/to/your/image.jpg"; // 替换成你的图像路径
    Mat image = Imgcodecs.imread(imagePath);

    if (image.empty()) {
        System.out.println("Could not read the image: " + imagePath);
        return;
    }

    Mat grayImage = new Mat();
    Imgproc.cvtColor(image, grayImage, Imgproc.COLOR_BGR2GRAY);

    Imgcodecs.imwrite("path/to/output/gray_image.jpg", grayImage); // 保存灰度图像
}

}
“`

4. 图像滤波:

使用 Imgproc.GaussianBlur() 函数进行高斯模糊。

“`java
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.core.Size;
import org.opencv.core.Core;

public class ImageBlurring {
public static void main(String[] args) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

    String imagePath = "path/to/your/image.jpg"; // 替换成你的图像路径
    Mat image = Imgcodecs.imread(imagePath);

    if (image.empty()) {
        System.out.println("Could not read the image: " + imagePath);
        return;
    }

    Mat blurredImage = new Mat();
    Imgproc.GaussianBlur(image, blurredImage, new Size(5, 5), 0); // 使用 5x5 的高斯核

    Imgcodecs.imwrite("path/to/output/blurred_image.jpg", blurredImage); // 保存模糊图像
}

}
“`

5. 边缘检测:

使用 Imgproc.Canny() 函数进行 Canny 边缘检测。

“`java
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.core.Core;

public class EdgeDetection {
public static void main(String[] args) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

    String imagePath = "path/to/your/image.jpg"; // 替换成你的图像路径
    Mat image = Imgcodecs.imread(imagePath);

    if (image.empty()) {
        System.out.println("Could not read the image: " + imagePath);
        return;
    }

    Mat grayImage = new Mat();
    Imgproc.cvtColor(image, grayImage, Imgproc.COLOR_BGR2GRAY);

    Mat edges = new Mat();
    Imgproc.Canny(grayImage, edges, 50, 150); // 使用 50 和 150 作为阈值

    Imgcodecs.imwrite("path/to/output/edges.jpg", edges); // 保存边缘图像
}

}
“`

三、对象识别 (使用预训练模型)

为了实现对象识别,我们可以使用 OpenCV 的 DNN (Deep Neural Network) 模块,加载预训练的深度学习模型。这里以加载 YOLO (You Only Look Once) 模型为例。

1. 下载 YOLO 模型文件:

  • 你需要下载 YOLO 的权重文件 (yolov3.weights) 和配置文件 (yolov3.cfg)。可以从 YOLO 的官方网站或相关资源网站下载。 确保下载的版本与你的代码兼容。

2. 加载 YOLO 模型:

“`java
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.core.MatOfFloat;
import org.opencv.core.MatOfInt;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.dnn.Dnn;
import org.opencv.dnn.Net;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.core.Core;

import java.util.ArrayList;
import java.util.List;

public class ObjectDetection {
private static final float CONFIDENCE_THRESHOLD = 0.5f;
private static final float NMS_THRESHOLD = 0.4f;

public static void main(String[] args) {
    System.loadLibrary(Core.NATIVE_LIBRARY_NAME);

    String imagePath = "path/to/your/image.jpg"; // 替换成你的图像路径
    String yoloConfigFile = "path/to/your/yolov3.cfg"; // 替换成你的 YOLO 配置文件路径
    String yoloWeightsFile = "path/to/your/yolov3.weights"; // 替换成你的 YOLO 权重文件路径
    String classesFile = "path/to/your/coco.names"; // 替换成你的 COCO classes 文件路径

    // 加载 COCO classes
    List<String> classes = loadClasses(classesFile);

    // 加载 YOLO 模型
    Net net = Dnn.readNetFromDarknet(yoloConfigFile, yoloWeightsFile);

    Mat image = Imgcodecs.imread(imagePath);

    if (image.empty()) {
        System.out.println("Could not read the image: " + imagePath);
        return;
    }

    // 创建输入 Blob
    Mat blob = Dnn.blobFromImage(image, 1.0 / 255.0, new org.opencv.core.Size(416, 416), new Scalar(0, 0, 0), true, false);

    // 设置输入 Blob
    net.setInput(blob);

    // 获取输出层名称
    List<String> outputLayers = net.getUnconnectedOutLayersNames();

    // 前向传播
    List<Mat> outputs = new ArrayList<>();
    net.forward(outputs, outputLayers);

    // 处理输出结果
    List<Integer> classIds = new ArrayList<>();
    List<Float> confidences = new ArrayList<>();
    List<Rect> boxes = new ArrayList<>();

    for (Mat output : outputs) {
        float[] data = new float[(int) (output.total() * output.channels())];
        output.get(0, 0, data);

        for (int i = 0; i < output.rows(); i++) {
            float confidence = data[i * output.cols() + 5]; // confidence score
            if (confidence > CONFIDENCE_THRESHOLD) {
                int classId = (int) (argMax(subarray(data, i * output.cols() + 5, i * output.cols() + output.cols()))[0]);
                float centerX = data[i * output.cols()];
                float centerY = data[i * output.cols() + 1];
                float width = data[i * output.cols() + 2];
                float height = data[i * output.cols() + 3];

                int x = (int) ((centerX - width / 2) * image.cols());
                int y = (int) ((centerY - height / 2) * image.rows());
                int w = (int) (width * image.cols());
                int h = (int) (height * image.rows());

                boxes.add(new Rect(x, y, w, h));
                classIds.add(classId);
                confidences.add(confidence);
            }
        }
    }

    // Non-Maximum Suppression (NMS)
    MatOfFloat confidencesMat = new MatOfFloat();
    confidencesMat.fromList(confidences);
    MatOfRect boxesMat = new MatOfRect();
    boxesMat.fromList(boxes);
    MatOfInt indicesMat = new MatOfInt();

    Dnn.NMSBoxes(boxesMat, confidencesMat, CONFIDENCE_THRESHOLD, NMS_THRESHOLD, indicesMat);

    int[] indices = indicesMat.toArray();

    // Draw bounding boxes
    for (int idx : indices) {
        Rect box = boxes.get(idx);
        int classId = classIds.get(idx);

        drawBoundingBox(image, classId, confidences.get(idx), box, classes);
    }

    Imgcodecs.imwrite("path/to/output/detected_objects.jpg", image);
    System.out.println("Object detection completed!");
}

private static void drawBoundingBox(Mat img, int classId, float confidence, Rect box, List<String> classes) {
    Imgproc.rectangle(img, box.tl(), box.br(), new Scalar(0, 255, 0), 2);

    String label = classes.get(classId) + ": " + String.format("%.2f", confidence);
    int[] baseLine = new int[1];
    org.opencv.core.Size labelSize = Imgproc.getTextSize(label, Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, 1, baseLine);

    Imgproc.rectangle(img, new Point(box.x, box.y - labelSize.height),
            new Point(box.x + labelSize.width, box.y + baseLine[0]), new Scalar(0, 255, 0), Imgproc.FILLED);
    Imgproc.putText(img, label, new Point(box.x, box.y), Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(0, 0, 0), 1);
}


private static List<String> loadClasses(String filePath) {
    List<String> classes = new ArrayList<>();
    try {
        java.nio.file.Files.lines(java.nio.file.Paths.get(filePath)).forEach(classes::add);
    } catch (java.io.IOException e) {
        e.printStackTrace();
    }
    return classes;
}

// Helper functions
private static double[] argMax(float[] array) {
    int best = -1;
    float best_confidence = 0.0f;
    for (int i = 0; i < array.length; i++) {
        float confidence = array[i];
        if (confidence > best_confidence) {
            best_confidence = confidence;
            best = i;
        }
    }

    return new double[]{best, best_confidence};
}

private static float[] subarray(float[] array, int startIndex, int endIndex) {
    float[] subArray = new float[endIndex - startIndex];
    System.arraycopy(array, startIndex, subArray, 0, endIndex - startIndex);
    return subArray;
}

}
“`

3. 代码解释:

  • Dnn.readNetFromDarknet(yoloConfigFile, yoloWeightsFile): 加载 YOLO 模型。
  • Dnn.blobFromImage(image, 1.0 / 255.0, new org.opencv.core.Size(416, 416), new Scalar(0, 0, 0), true, false): 将图像转换为 Blob 格式,这是深度学习模型所需的输入格式。
  • net.setInput(blob): 设置模型的输入。
  • net.forward(outputs, outputLayers): 执行前向传播,得到模型的输出。
  • 后续代码解析模型的输出,提取检测到的对象的位置、类别和置信度。
  • Dnn.NMSBoxes(): 执行非极大值抑制 (Non-Maximum Suppression),去除重叠的边界框。
  • drawBoundingBox(): 在图像上绘制边界框和标签。

4. Classes 文件
你还需要下载 coco.names 文件,该文件包含 COCO 数据集中所有类别的名称。将此文件放置在你的项目目录中,并更新代码中的 classesFile 变量指向它的正确位置。 coco.names 内容类似于:

person
bicycle
car
motorcycle
airplane
bus
train
truck
boat
traffic light
fire hydrant
...

四、总结与展望

本教程介绍了如何使用 OpenCV Java 接口进行基本的图像处理和对象识别。 你已经学会了:

  • 配置 OpenCV Java 开发环境。
  • 加载、显示、转换和滤波图像。
  • 使用预训练的 YOLO 模型进行对象识别。

下一步,你可以尝试以下方向:

  • 探索更多的图像处理算法,例如边缘检测、形态学操作、图像分割等。
  • 使用不同的预训练模型,例如 SSD, Faster R-CNN 等,进行更复杂的目标检测任务。
  • 训练自己的对象识别模型,以识别特定的对象。
  • 将图像识别技术应用到实际项目中,例如智能监控、人脸识别、自动驾驶等。

OpenCV 提供了强大的图像处理和计算机视觉功能,通过不断学习和实践,你可以构建出更加智能和强大的图像识别应用。 记住不断查阅 OpenCV 的官方文档和示例代码,这将对你的学习过程非常有帮助。 祝你学习顺利!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部