JavaCV实现旋转图像识别和旋转角度预测

发布时间 2023-07-10 11:09:07作者: 簡素

引言

本文将介绍如何使用JavaCV库来实现图像识别和旋转角度预测,并结合直方图统计和dhash算法来比较图片的相似度。JavaCV是一个基于OpenCV的Java库,提供了丰富的图像处理和计算机视觉功能。

环境搭建

在开始之前,需要安装JavaCV库和相关依赖。可以通过Maven或手动下载jar包的方式进行安装。安装完成后,配置JavaCV的开发环境,确保能够成功引入相关的类和方法。这里是使用maven的方式引入最小依赖。
JavaCV图片处理最小依赖如下:

<profiles>
        <profile>
            <id>dev</id>
            <properties>
                <system.platform>macosx-arm64</system.platform>
            </properties>
        </profile>
        <profile>
            <id>pro</id>
            <activation>
                <property>
                    <name>pro</name>
                </property>
            </activation>
            <properties>
                <system.platform>linux-x86_64</system.platform>
            </properties>
        </profile>
    </profiles>
<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <maven.compiler.source>1.8</maven.compiler.source>
  <maven.compiler.target>1.8</maven.compiler.target>
  <javacv.version>1.5.7</javacv.version>
  <opencv.version>4.5.5-${javacv.version}</opencv.version>
  <openblas.version>0.3.19-${javacv.version}</openblas.version>
</properties>
<dependencies>
  <!-- 全量依赖不建议使用 打包后有约1G大小-->
  <!--   <dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>javacv-platform</artifactId>
    <version>1.5.9</version>
  </dependency> -->
    <!--    Maven 版本的最小依赖   -->
    <!--  javacv核心依赖 -->
    <dependency>
      <groupId>org.bytedeco</groupId>
      <artifactId>javacv</artifactId>
      <version>${javacv.version}</version>
     <!-- 排除不需要的依赖   -->
      <exclusions>
        <exclusion>
          <groupId>org.bytedeco</groupId>
          <artifactId>ffmpeg</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.bytedeco</groupId>
          <artifactId>videoinput</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.bytedeco</groupId>
          <artifactId>tesseract</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.bytedeco</groupId>
          <artifactId>flycapture</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.bytedeco</groupId>
          <artifactId>flycapture</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.bytedeco</groupId>
          <artifactId>artoolkitplus</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.bytedeco</groupId>
          <artifactId>leptonica</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.bytedeco</groupId>
          <artifactId>flandmark</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.bytedeco</groupId>
          <artifactId>librealsense</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.bytedeco</groupId>
          <artifactId>librealsense</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.bytedeco</groupId>
          <artifactId>libfreenect</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.bytedeco</groupId>
          <artifactId>librealsense2</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.bytedeco</groupId>
          <artifactId>libfreenect2</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.bytedeco</groupId>
          <artifactId>libdc1394</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.bytedeco</groupId>
      <artifactId>javacpp</artifactId>
      <version>${javacv.version}</version>
		<!--  classifier用于指定系统环境,具体有哪些可以先引入javacv-platform查看  -->
      <classifier>${system.platform}</classifier>
    </dependency>
    <!--  javacv核心依赖 -->
    <!-- opencv依赖 -->
    <dependency>
      <groupId>org.bytedeco</groupId>
      <artifactId>opencv</artifactId>
      <version>${opencv.version}</version>
      <classifier>${system.platform}</classifier>
    </dependency>
    <!-- 矩阵计算库 -->
    <dependency>
      <groupId>org.bytedeco</groupId>
      <artifactId>openblas</artifactId>
      <version>${openblas.version}</version>
      <classifier>${system.platform}</classifier>
    </dependency>
</dependencies>

GitHub - bytedeco/javacv: Java interface to OpenCV, FFmpeg, and more
GitHub - bytedeco/javacv-examples: Examples of using JavaCV / OpenCV library on Java Virtual Machine

图像旋转前后的识别匹配

在图像识别中,常用的方法之一是通过直方图统计来衡量图像之间的相似度。直方图统计可以将图像的颜色分布进行量化,然后通过比较直方图的差异来判断图像的相似程度。因为直方图统计的是图片的颜色分布,与图片像素的位置无关,所以对于旋转前后的图片用直方图的差异来判断图像的相似程度能达到比较好的效果。
JavaCV提供了直方图统计的功能,可以使用其提供的API来计算图像的直方图,并进行比较。

import org.bytedeco.javacpp.FloatPointer;
import org.bytedeco.javacpp.IntPointer;
import org.bytedeco.javacpp.PointerPointer;
import org.bytedeco.opencv.global.opencv_core;
import org.bytedeco.opencv.global.opencv_imgproc;
import org.bytedeco.opencv.opencv_core.*;

import static org.bytedeco.opencv.global.opencv_core.*;
import static org.bytedeco.opencv.global.opencv_imgproc.*;
import static org.bytedeco.opencv.global.opencv_imgcodecs.*;
import static org.bytedeco.opencv.global.opencv_imgproc.calcHist;

public void imageFeature() {
    // 加载图像
    Mat image1 = imread("image/a1.jpeg", IMREAD_COLOR);
    Mat hist1 = calcHist01(image1);
    for (int angle = 0; angle < 360; angle++) {
        Mat image2 = rotateImage(image1, angle);
        Mat hist2 = calcHist01(image2);
        // 比较直方图相似度(使用巴氏距离,距离越小越相似)
        double similarity = compareHist(hist1, hist2, opencv_imgproc.HISTCMP_BHATTACHARYYA);
        System.out.println(angle + ": " + similarity);
    }
}
public Mat calcHist01(Mat image) {
final int[] channels = new int[]{0,1,2};
final Mat mask = new Mat();
final Mat hist = new Mat();
final int[] histSize = new int[]{16,16,16};
final float[] histRange = new float[]{0f, 255f};
IntPointer intPtrChannels = new IntPointer(channels);
IntPointer intPtrHistSize = new IntPointer(histSize);
final PointerPointer<FloatPointer> ptrPtrHistRange = new PointerPointer<>(histRange, histRange, histRange);
calcHist(image, 1, intPtrChannels, mask, hist, 3, intPtrHistSize, ptrPtrHistRange, true, false);
return hist;
}

图片相似度比较

 除了直方图统计,还可以使用dhash算法来比较两个图片的相似度。dhash算法是一种基于哈希值的图像相似度计算方法,它通过计算图片的差异哈希值来评估图片的相似程度。dhash对于图像缩放、模糊的情况下有较高的识别率,但dhash由于是比较每行左右两个像素的差异,对像素的位置是敏感的,所以对于旋转前后的图片像素位置变化,使用dhash则达不到预想的效果。
由于JavaCV没有直接提供计算图片的dhash值的方法,这里需自己实现图片dhash的计算,然后再计算两个hash的汉明距离,汉明距离为0则最相似。
public static double calculateDHashSimilarity(Mat img1, Mat img2) {
    long hash1 = calculateDHash(img1);
    long hash2 = calculateDHash(img2);
    int hammingDistance = Long.bitCount(hash1 ^ hash2);
    double similarity = hammingDistance / 64.0;
    return similarity;
}
public static long calculateDHash(Mat image) {
        Mat resizedImage = new Mat();
        Size newSize = new Size(9, 8);
        opencv_imgproc.resize(image, resizedImage, newSize, 0, 0, INTER_AREA);
        long hash = 0;
        for (int row = 0; row < 8; row++) {
            for (int col = 0; col < 8; col++) {
                double leftPixel = resizedImage.ptr(row, col).getDouble();
                double rightPixel = resizedImage.ptr(row, col + 1).getDouble();
                hash = (hash << 1) | (leftPixel < rightPixel ? 1 : 0);
            }
        }
        return hash;
    }

旋转角度预测

对于图片旋转角度的预测可以使用机器学习的方式达到预测效果。RotNet就是一种自监督模型。这里推荐一个Python的实现,感兴趣的可以了解一下(https://github.com/d4nst/RotNet/)。由于RotNet,达不到我预期的效果,这里使用前面提到的直方图统计比较旋转前后的图片找到原图,然后用暴力破解的方式将旋转后的图片每次旋转两度与原图匹配计算显示度,然后取最小相似度的旋转角度作为预测角度。

// 部分代码
int minAngel = 0;
double minRotateSimilarity = 999999999;
for (int angle = 1; angle < 360; angle += 2) {
    // 旋转
    Mat matRotate = rotateImage(imgMatRandom, -angle);
    double similarity = calculateDHashSimilarity(matRotate, matchMat);
    if (similarity < minRotateSimilarity) {
        minRotateSimilarity = similarity;
        minAngel = angle;
    }
}

private static Mat rotateImage(Mat image, double angle) {
        Point2f center = new Point2f(image.cols() / 2, image.rows() / 2);
        Mat rotationMatrix = getRotationMatrix2D(center, angle, 1.0);
        Mat rotatedImage = new Mat();
        warpAffine(image, rotatedImage, rotationMatrix, image.size());
        return rotatedImage;
}