You need to enable JavaScript to run this app.
导航

图片预处理指南

最近更新时间2023.02.24 15:14:53

首次发布时间2023.02.24 15:14:53

请求API接口的图片过大时会导致网络传输耗时比较大,接口耗时比较长。这里提供一些图像预处理的方法,通过对原始图像进行缩放、压缩来降低请求API的图片大小。

预处理过程

1、Exif方向校正,去除Exif方向信息
2、图像缩放,长边最大不超过2048
3、JPEG图像压缩
4、图像Base64编码

注意:
请根据业务数据情况,调整图像缩放尺寸、JPEG图像压缩系数;

代码示例

Python

依赖Pillow,pip install --upgrade Pillow

import os
import base64
from io import BytesIO
from PIL import Image, ImageOps


def resize(image, max_size=2048):
    # 原图的宽、高
    w, h = image.width, image.height
    max_wh = max(w, h)
    if max_wh < max_size:
        print(f"image max side is: {max_wh} < {max_size}, not resize")
        return image
    
    ratio = float(max_size) / (max(float(w), float(h)))
    width = int(w * ratio)
    height = int(h * ratio)
    print(f"target image width: {width}, height: {height}")

    # resample: 图像插值算法
    # Image.Resampling.NEAREST: 最近邻插值
    # Image.Resampling.BILINEAR: 双线性插值
    # Image.Resampling.BICUBIC: 双三次插值
    return image.resize(size=(width, height), resample=Image.Resampling.NEAREST)


if __name__ == "__main__":
    img_path = "demo.jpeg"
    file_size = os.path.getsize(img_path)
    print(f"file size: {file_size} 字节")

    image = Image.open(img_path)
    # 1、根据Exif中的方向信息把图片转成正向
    image = ImageOps.exif_transpose(image)
    print(f"image format: {image.format}, width: {image.width}, height: {image.height}")

    # 2、图片缩放,长边最大到2048
    image = resize(image, max_size=2048)

    # 3、保存图片
    # 3.1 保存到本地磁盘
    # 保存成JPG格式,压缩系数quality=85,取值范围0~95,数字越大对图像压缩越小(图像质量越好)
    image.save("image.jpg", format="JPEG", quality=85, subsampling=2)

    # 3.2 获取保存的图片的二进制数据
    with BytesIO() as output:
        image.save(output, format="JPEG", quality=90, subsampling=2)
        contents = output.getvalue()
        print(f"saved image file size: {len(contents)} 字节")

    # 4、Base64编码
    img_base64 = base64.b64encode(contents)
    print(f"image base64 encoded size: {len(img_base64)} 字节, base64 prefix: {img_base64[:10]}")

Go

依赖gocv和第三方base64

package main

import (
   b64 "encoding/base64"
   "fmt"
   "image"
   "math"
   "os"
   "time"

   "github.com/cristalhq/base64"
   "gocv.io/x/gocv"
)

func Max(x, y int) int {
   if x > y {
      return x
   }
   return y
}

func Resize(mat gocv.Mat, maxSize int) gocv.Mat {
   // 原图的宽、高
   w := mat.Cols()
   h := mat.Rows()
   maxWH := Max(w, h)
   if maxWH < maxSize {
      fmt.Printf("image max side is: %d < %d, not resize\n", maxWH, maxSize)
      return mat
   }

   ratio := float64(maxSize) / (math.Max(float64(w), float64(h)))
   width := int(float64(w) * ratio)
   height := int(float64(h) * ratio)
   fmt.Printf("target image width: %d, height: %d\n", width, height)

   sz := image.Point{X: width, Y: height}
   gocv.Resize(mat, &mat, sz, 0, 0, gocv.InterpolationLinear)

   return mat
}

func main() {
   imgPath := "demo.jpeg"

   fileInfo, err := os.Stat(imgPath)
   if err != nil {
      fmt.Printf("Error: %v\n", err)
      os.Exit(-1)
   }

   fmt.Printf("image file size: %d 字节\n", fileInfo.Size())

   data, err := os.ReadFile(imgPath)
   if err != nil {
      fmt.Printf("Error: %v\n", err)
      os.Exit(-1)
   }

   // 1.1 从文件读取图片(gocv会根据Exif方向信息把图片转正)
   //mat := gocv.IMRead(imgPath, gocv.IMReadColor)
   //defer mat.Close()

   // 1.2 从二进制数据解码图片
   mat, err := gocv.IMDecode(data, gocv.IMReadColor)
   defer func() {
      if err != nil {
         mat.Close()
      }
   }()

   fmt.Printf("image width: %d, height: %d\n", mat.Cols(), mat.Rows())

   // 2 图片缩放,长边最大到2048
   mat = Resize(mat, 2048)
   fmt.Printf("after resize, image width: %d, height: %d\n", mat.Cols(), mat.Rows())

   // 3 保存图片
   // 保存成JPG格式,压缩系数quality=85,取值范围0~95,数字越大对图像压缩越小(图像质量越好)
   quality := 85
   buf, err := gocv.IMEncodeWithParams(gocv.JPEGFileExt, mat, []int{gocv.IMWriteJpegQuality, quality})
   defer buf.Close()

   // 如果需要保存到本地磁盘
   err = os.WriteFile("image.jpg", buf.GetBytes(), 0644)
   if err != nil {
      fmt.Printf("Error: %v\n", err)
      os.Exit(-1)
   }

   // Base64编码
   {
      t := time.Now()
      imgBase64 := b64.StdEncoding.EncodeToString(buf.GetBytes())
      fmt.Printf("image base64 encoded size: %d 字节, base64 prefix: %s, cost: %d Microseconds\n", len(imgBase64), imgBase64[:10], time.Since(t).Microseconds())
   }
   // 更快的方式
   {
      t := time.Now()
      imgBase64 := base64.StdEncoding.EncodeToString(buf.GetBytes())
      fmt.Printf("image base64 encoded size: %d 字节, base64 prefix: %s, cost: %d Microseconds\n", len(imgBase64), imgBase64[:10], time.Since(t).Microseconds())
   }
}

Java

依赖Java封装的OpenCV
依赖commons-io
依赖commons-codec
OpenCV使用参考:https://www.baeldung.com/java-opencv、https://github.com/openpnp/opencv
Java Base64编解码:https://www.baeldung.com/java-base64-encode-and-decode

Java代码

package org.example;

import nu.pattern.OpenCV;
import org.apache.commons.io.FileUtils;
import org.apache.commons.codec.binary.Base64;
import org.opencv.core.*;
import org.opencv.imgproc.*;
import org.opencv.imgcodecs.*;

import java.io.File;
import java.io.IOException;

public class Main {

    public static Mat Resize(Mat mat, int maxSize) {
        // 原图的宽、高
        int w = mat.cols();
        int h = mat.rows();
        int maxWH = Math.max(w, h);
        if (maxWH < maxSize) {
            System.out.printf("image max side is: %d < %d, not resize\n", maxWH, maxSize);
            return mat;
        }

        double ratio = (double) maxSize / Math.max(w, h);
        int width = (int) (w * ratio);
        int height = (int) (h * ratio);
        System.out.printf("target image width: %d, height: %d\n", width, height);

        Size size = new Size(width, height);
        Imgproc.resize(mat, mat, size, 0, 0, Imgproc.INTER_LINEAR);

        return mat;
    }

    public static void main(String[] args) {
        // 加载OpenCV动态库
        OpenCV.loadShared();

        String imgPath = "demo.jpg";
        System.out.printf("image file size: %d 字节\n", FileUtils.sizeOf(new File(imgPath)));

        // 1.1 从文件读取图片(OpenCV 会根据Exif方向信息把图片转正)
        Mat mat1 = Imgcodecs.imread(imgPath, Imgcodecs.IMREAD_COLOR);
        System.out.printf("image width: %d, height: %d\n", mat1.cols(), mat1.rows());

        // 1.2 从二进制数据解码图片
        try {
            // 读取图片,获取二进制数据
            byte[] bytes = FileUtils.readFileToByteArray(new File(imgPath));
            Mat mat2 = Imgcodecs.imdecode(new MatOfByte(bytes), Imgcodecs.IMREAD_COLOR);
            System.out.printf("image width: %d, height: %d\n", mat2.cols(), mat2.rows());
        } catch (IOException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }

        // 2 图片缩放,长边最大到2048
        Mat mat3 = Resize(mat1, 2048);
        System.out.printf("after resize, image width: %d, height: %d\n", mat3.cols(), mat3.rows());

        // 3 保存图片
        // 保存成JPG格式,压缩系数quality=85,取值范围0~95,数字越大对图像压缩越小(图像质量越好)
        String outPath = "out_85.jpg";
        int quality = 85;
        MatOfInt map = new MatOfInt(Imgcodecs.IMWRITE_JPEG_QUALITY, quality);
        Imgcodecs.imwrite(outPath, mat3, map);

        String outPath1 = "out_100.jpg";
        quality = 100;
        MatOfInt map1 = new MatOfInt(Imgcodecs.IMWRITE_JPEG_QUALITY, quality);
        Imgcodecs.imwrite(outPath1, mat3, map1);

        // 获取编码压缩图的二进制数据
        MatOfByte bytemat = new MatOfByte();
        Imgcodecs.imencode(".jpg", mat3, bytemat, map);
        byte[] bytes = bytemat.toArray();

        // 把二进制数据保存到文件
        File outputFile = new File("out_85_buf.jpg");
        try {
            FileUtils.writeByteArrayToFile(outputFile, bytes);
        } catch (IOException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }

        // Base64编码
        Base64 base64 = new Base64();
        String encodedString = new String(base64.encode(bytes));
        System.out.printf("image base64 encoded size: %d 字节, base64 prefix: %s\n", encodedString.length(), encodedString.substring(0, 10));
    }
}

maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>ImageProc</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.openpnp</groupId>
            <artifactId>opencv</artifactId>
            <version>4.6.0-0</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.11.0</version>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.15</version>
        </dependency>
    </dependencies>

</project>