業務:找出兩個表的重復的圖片。
圖片在表里存的是二進制值,存在大量由于一些特殊情況例如掃描有差異,導致圖片存的二進制值不同,但圖片其實是一樣來的。
所以找出兩個表重復相同的圖片,不可能只是單純的比較二進制值相等。
方法:針對這種情況,使用OpenCV直方圖算法可以比較兩張圖片的相似度,測試發現完全相同的圖片相似度等于1(表里存的二進制值不相等)
實操:Java引入使用opencv步驟詳解
1.引入opencv依賴
<!-- https://mvnrepository.com/artifact/org.openimaj/core -->
<dependency><groupId>org.openpnp</groupId><artifactId>opencv</artifactId><version>4.5.5-1</version>
</dependency>
2.代碼Demo
opencv提供了均方差算法(MSE)、結構相似性指數算法(SSIM)、峰值信噪比算法(PSNR)、直方圖算法(SSIM-WH),其中使用直方圖算法來比較圖片相似效果最好。
public static void main(String[] args) {// 加載OpenCV庫System.loadLibrary(Core.NATIVE_LIBRARY_NAME);// 讀取兩張圖像。準備比對的圖片Mat image1 = Imgcodecs.imread("D:\\work\\testdata\\psc_1716260008343.jpg");Mat image2 = Imgcodecs.imread("D:\\work\\testdata\\psc_1716260008345.jpg");// 將圖片處理成一樣大Imgproc.resize(image1, image1, image2.size());Imgproc.resize(image2, image2, image1.size());// 計算均方差(MSE)double mse = calculateMSE(image1, image2);System.out.println("均方差(MSE): " + mse);// 計算結構相似性指數(SSIM)double ssim = calculateSSIM(image1, image2);System.out.println("結構相似性指數(SSIM): " + ssim);// 計算峰值信噪比(PSNR)double psnr = calculatePSNR(image1, image2);System.out.println("峰值信噪比(PSNR): " + psnr);// 計算直方圖final double similarity = calculateHistogram(image1, image2);System.out.println("圖片相似度(直方圖): " + similarity);// 計算歸一化交叉相關(NCC)
// double ncc = calculateNCC(image1, image2);
// System.out.println("歸一化交叉相關(NCC): " + ncc);}// 計算均方差(MSE)private static double calculateHistogram(Mat image1, Mat image2) {// 計算直方圖Mat hist1 = calculateHistogram(image1);Mat hist2 = calculateHistogram(image2);// 計算相似度final double similarity = Imgproc.compareHist(hist1, hist2, Imgproc.CV_COMP_CORREL);// 手動釋放內存
// if (hist1 != null) {
// hist1.release();
// }
// if (hist2 != null) {
// hist2.release();
// }return similarity;}// 計算均方差(MSE)private static double calculateMSE(Mat image1, Mat image2) {Mat diff = new Mat();Core.absdiff(image1, image2, diff);Mat squaredDiff = new Mat();Core.multiply(diff, diff, squaredDiff);Scalar mseScalar = Core.mean(squaredDiff);return mseScalar.val[0];}// 計算結構相似性指數(SSIM)private static double calculateSSIM(Mat image1, Mat image2) {Mat image1Gray = new Mat();Mat image2Gray = new Mat();Imgproc.cvtColor(image1, image1Gray, Imgproc.COLOR_BGR2GRAY);Imgproc.cvtColor(image2, image2Gray, Imgproc.COLOR_BGR2GRAY);MatOfFloat ssimMat = new MatOfFloat();Imgproc.matchTemplate(image1Gray, image2Gray, ssimMat, Imgproc.CV_COMP_CORREL);Scalar ssimScalar = Core.mean(ssimMat);return ssimScalar.val[0];}// 計算峰值信噪比(PSNR)private static double calculatePSNR(Mat image1, Mat image2) {Mat diff = new Mat();Core.absdiff(image1, image2, diff);Mat squaredDiff = new Mat();Core.multiply(diff, diff, squaredDiff);Scalar mseScalar = Core.mean(squaredDiff);double mse = mseScalar.val[0];double psnr = 10.0 * Math.log10(255.0 * 255.0 / mse);return psnr;}// 計算歸一化交叉相關(NCC)
// private static double calculateNCC(Mat image1, Mat image2) {
// Mat image1Gray = new Mat();
// Mat image2Gray = new Mat();
// Imgproc.cvtColor(image1, image1Gray, Imgproc.COLOR_BGR2GRAY);
// Imgproc.cvtColor(image2, image2Gray, Imgproc.COLOR_BGR2GRAY);
// MatOfInt histSize = new MatOfInt(256);
// MatOfFloat ranges = new MatOfFloat(0, 256);
// Mat hist1 = new Mat();
// Mat hist2 = new Mat();
//
// Core.normalize(hist1, hist1, 0, 1, Core.NORM_MINMAX);
// Core.normalize(hist2, hist2, 0, 1, Core.NORM_MINMAX);
// double ncc = Core.compareHist(hist1, hist2, Imgproc.CV_COMP_CORREL);
// return ncc;
// }private static Mat calculateHistogram(Mat image) {Mat hist = new Mat();// 設置直方圖參數MatOfInt histSize = new MatOfInt(256);MatOfFloat ranges = new MatOfFloat(0, 256);MatOfInt channels = new MatOfInt(0);List<Mat> images = new ArrayList<>();images.add(image);// 計算直方圖Imgproc.calcHist(images, channels, new Mat(), hist, histSize, ranges);return hist;}
3.運行遇到的報錯問題以及解決方法
Exception in thread "main" java.lang.UnsatisfiedLinkError: no opencv_java455 in java.library.path
報錯原因:
在JDK bin 目錄下找不到 opencv_java455.dll 文件
解決方法:
官網下載地址:Releases - OpenCV
找到對應的版本下載opencv(如果下載不起很慢,可以復制鏈接用迅雷下載)
雙擊打開安裝包選擇安裝提取目錄
等待解壓
在目錄找到dll文件
然后復制到jdk bin目錄中
再重新運行程序即可解決
4.運行
均方差算法(MSE):
計算兩幅圖片每個像素之間的差異,并計算它們的平均值。MSE值越小,表示兩幅圖片越相似。
結構相似性指數(SSIM):
通過比較兩幅圖片的亮度、對比度和結構信息來評估它們的相似性。值越大,越相似。
峰值信噪比(PSNR):
通過計算兩幅圖片的MSE值,并將其轉換為對數尺度,來評估它們的相似性。PSNR值越大,表示兩幅圖片越相似。
圖片相似度(直方圖):
通過將SSIM指數和直方圖相似性組合起來,來評估兩幅圖片的相似性。返回的相似性度量值越接近1,表示兩幅圖像越相似。
5.結合業務實現代碼片段
注:務必手動釋放Mat內存,親測不寫手動釋放內存,隨著循環量越多,創建Mat越多,就會導致內存崩潰泄露(按理說Java有回收機制,但我經過測試發現并沒有觸發回收內存,即使是沒用的Mat)
byte[] ecf2Image = bsImage.getImage();
byte[] upsImage = upsPage.getScanPage();
// 1.先直接對比ecf2和ups圖片的二進制值
if (Arrays.equals(ecf2Image, upsImage)) {// 二進制值相等則給ecf2圖片狀態更新為重復的updateAndRecord(shipmentNo, filename, upsPage, 1);// 然后跳出scanPageList的循環,已經確認為重復就不用再去匹配break;
}// 2.byte值不等,再用OpenCV來比較
// 將圖片二進制數據轉換為Mat對象
Mat image1 = Imgcodecs.imdecode(new MatOfByte(ecf2Image), Imgcodecs.IMREAD_COLOR);
Mat image2 = Imgcodecs.imdecode(new MatOfByte(upsImage), Imgcodecs.IMREAD_COLOR);
try {// 將圖片處理成一樣大Imgproc.resize(image1, image1, image2.size());Imgproc.resize(image2, image2, image1.size());// 計算直方圖final double similarity = calculateHistogram(image1, image2);if (similarity == 1) {// 更新狀態為重復的updateAndRecord(shipmentNo, filename, upsPage, 2);break;}
} catch (Exception e) {e.printStackTrace();
} finally {// 手動釋放內存if (image1 != null) {image1.release();}if (image2 != null) {image2.release();}
}