请求API接口的图片过大时会导致网络传输耗时比较大,接口耗时比较长。这里提供一些图像预处理的方法,通过对原始图像进行缩放、压缩来降低请求API的图片大小。
1、Exif方向校正,去除Exif方向信息
2、图像缩放,长边最大不超过2048
3、JPEG图像压缩
4、图像Base64编码
注意:
请根据业务数据情况,调整图像缩放尺寸、JPEG图像压缩系数;
依赖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]}")
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封装的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
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));
}
}
<?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>