背景:最近手头的项目遇到一个需求,要从一张图片中识别出身份证并且截取出来,生成一张与真实的身份证1:1大小的图片。开始我们找了几家第三方的解决方案,根据图片直接返回识别并还原的图片,但是效果并不理想,唯一一家识别效果好的平台,只返回图片中身份证4个角的像素坐标,所以裁剪和还原的任务就需要我们自己去解决。
任务
已知图片中的4个像素坐标,将身份证截取出来,拉伸还原成正常比例,如下图:
难题
由于图片的拍摄角度各有不同,身份证的形状也是多种多样,算法需要适用从不同角度下拍摄的照片。
过程
已知ABCD4个点的坐标分别为: (a1, a2), (b1, b2), (c1, c2), (d1, d2)
步骤一:新建一张画布,身份证的真实比例是 长度8.56cm×宽度5.4cm,所以我们新建一张856*540像素的画布:
BufferedImage nImg = new BufferedImage(856, 540, type);
步骤二:得到一张空白画布后,我们试着用二分法先填充画布中线的像素,如下图,将EF线排列到GH线上
已知ABCD四个点的坐标,我们可以得出AC线与BD线的方程式,可以得到E和F的坐标(e1,e2),(f1,f2):
//计算左线中点
double y1 = middleY * (ldy - luy) / img2.getHeight() + luy;
double x1 = (y1 - luy) * (ldx - lux) / (ldy - luy) + lux;
//计算右线中点
double y2 = middleY * (rdy - ruy) / img2.getHeight() + ruy;
double x2 = (y2 - ruy) * (rdx - rux) / (rdy - ruy) + rux;
从左到右遍历G到H的像素,根据比例获取EF线上的像素点,填充到GH线的像素上,由于GH的长度与EF的长度不同,所以不能一个像素对应一个像素依次排列,需要根据比例来获取像素,虽然会产生一个旧的像素填充到多个新的像素上导致的微量模糊,但这也是拉伸的过程中不可避免的。
填充完GH线之后,使用递归的思想,将ABEF填充到新图,以此类推,最终每一行的像素得以填充,算法结束。
我将整个过程封装成了一个JAVA类,有需要的可以直接拷贝使用。
package com.example.demo;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
/**
* @Author qiumaolong
* @Date 2019/5/22 14:38
**/
public class ImageCorrection {
/**
* 原文件
*/
private String originalFile;
BufferedImage image;
/**
* 左上角坐标
*/
private double lux ;
private double luy;
/**
* 右上角坐标
*/
private double rux;
private double ruy;
/**
* 左下角坐标
*/
private double ldx;
private double ldy;
/**
* 右下角坐标
*/
private double rdx;
private double rdy;
/**
* 目标图片规格
*/
private int width;
private int height;
public ImageCorrection(String originalFile) {
this.originalFile = originalFile;
try {
image = ImageIO.read(new File(originalFile));
lux = 0;
luy = 0;
rux = image.getWidth();
ruy = 0;
ldx = 0;
ldy = image.getHeight();
rdx = image.getWidth();
rdy = image.getHeight();
width = image.getWidth();
height = image.getHeight();
}catch (IOException ioe){
throw new RuntimeException("读取图片失败");
}
}
public void setRange(double lux, double luy, double rux, double ruy, double ldx, double ldy, double rdx, double rdy){
this.lux = lux;
this.luy = luy;
this.rux = rux;
this.ruy = ruy;
this.ldx = ldx;
this.ldy = ldy;
this.rdx = rdx;
this.rdy = rdy;
}
public void setTargetImgSize(int width, int height){
this.width = width;
this.height = height;
}
public void action(String fileSavePath){
try {
File file = new File(fileSavePath);
action(new FileOutputStream(file));
}catch (Exception fe){
throw new RuntimeException("图片保存失败");
}
}
private void action(OutputStream os){
BufferedImage nImg = new BufferedImage(width, height, image.getType());
fillingPixel(image, nImg, 0, height);
try {
ImageIO.write(nImg, "jpg", os);
}catch (IOException e){
throw new RuntimeException("图片保存失败");
}
}
private void splice(OutputStream os) {
//截取图像
BufferedImage image1 = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
Graphics2D g2 = image1.createGraphics();
image1 = g2.getDeviceConfiguration().createCompatibleImage(image.getWidth(), image.getHeight(), Transparency.TRANSLUCENT);
g2.dispose();
for (int i = 0; i < image1.getWidth(); i++) {
for (int j = 0; j < image1.getHeight(); j++) {
if (!ifWithinRange(i, j)){
continue;
}
image1.setRGB(i, j, image.getRGB(i, j));
}
}
try {
ImageIO.write(image1, "jpg", os);
}catch (IOException e){
throw new RuntimeException("图片保存失败");
}
}
/**
* 将不规则img1像素填充到规则的img2中
* 用二分线不断递归填充实现最终结果
* @param img1
* @param img2
* @param minY
* @param maxY
*/
private void fillingPixel(BufferedImage img1, BufferedImage img2, int minY, int maxY){
if (minY == maxY || minY == maxY - 1){
return;
}
int middleY = (minY + maxY) / 2;
//计算左线中点
double y1 = middleY * (ldy - luy) / img2.getHeight() + luy;
double x1 = (y1 - luy) * (ldx - lux) / (ldy - luy) + lux;
//计算右线中点
double y2 = middleY * (rdy - ruy) / img2.getHeight() + ruy;
double x2 = (y2 - ruy) * (rdx - rux) / (rdy - ruy) + rux;
for (int i=0; i< img2.getWidth(); i++){
int x = new Double(i * (x2 - x1) / img2.getWidth() + x1).intValue();
int y = new Double((x - x1) * (y2 - y1) / (x2 - x1) + y1).intValue();
img2.setRGB(i, middleY, img1.getRGB(x, y));
}
fillingPixel(img1, img2, minY, middleY);
fillingPixel(img1, img2, middleY, maxY);
}
private boolean ifWithinRange(int x, int y){
double n = 0.0;
//是否在左线右边
n = (y - ldy)*(lux -ldx)/(luy - ldy) + ldx;
if (x < n){
return false;
}
//是否在右线左边
n = (y - ruy) * (rdx - rux) / (rdy - ruy) + rux;
if (x > n){
return false;
}
//是否在上线下边
n = luy + (ruy - luy)/(rux - lux)*(x - lux);
if (y < n){
return false;
}
//是否在下线上边
if (y > ldy + ((rdy - ldy) / (rdx - ldx) * (x - ldx))){
return false;
}
return true;
}
}
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付