请求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>