在做人臉識別專案中,首先需要註冊人臉,目前程式要求在指定的資料夾存入檔案格式為JPG的圖片即完成人臉註冊。使用中,部分使用者對JPG格式理解不深,誤以為只要以“.JPEG”或者“.jpg”結尾的圖片就是JPG格式,甚至有使用者還特意把“張三 .png”改成“張三.jpg”偽裝成JPG格式來滿足要求。

 其實副檔名(.jpg)與檔案格式無關,是人們為了便於區分,強加的副檔名。就像linux系統裡檔案根本就沒有副檔名的。那麼如何通過程式碼判斷該圖片檔案是否是JPG呢?我們需要了解圖片檔案的儲存,bmp,jpg,png圖片儲存差異很大,但是歸結起來主要分為三部分:檔案頭,調色盤,資料區;我們可以通過讀取檔案頭來判斷該檔案的格式,最後會按照這種思路封裝的一個工具類,主要用於bmp,png轉換成JPG。

如何快速確認一張圖片的檔案格式呢?其實任意一款文字編輯器都可以讀取檔案頭,下面以EditPlus為例;選中圖片右鍵用EditPlus開啟(彈框,選擇“否”),然後一堆亂碼出來了,然後從亂碼裡尋找下檔案頭即可。下面三張副檔名均為JPG的圖片中只有一張檔案格式為JPG的。

奶茶01.jpg,副檔名與檔案格式一致的圖片


王俊凱.jpg,副檔名.jpg,檔案格式為PNG:

.

周杰倫.jpg,副檔名為.jpg,實際檔案格式為BMP


揪出兩張偽JPG圖片檔案,如何快速修改檔案格式呢?常規做法就是用PS(或畫圖板)開啟圖片然後另存為JPG格式。好吧,說好的快速呢,PS也叫快啊?!其實吧,有一項你習焉不察的鵝廠黑科技——QQ截圖儲存型別選擇JPEG,檔名為”張三2.jpg“。然後把截圖的照片存入人臉註冊指定資料夾,終於可以識別了。

下面我們就通過程式碼方式來修改圖片檔案為JPG

package com.interjoy.jardemo;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;

/**
 * Title:
 * Description: 主要用於圖片格式轉換bmp/png-->JPG
 * Company: 北京xxxx科技有限公司,[email protected]
 *
 * @author Created by ylwang on 2018/1/24
 */
public class PicUtils {
    private static final String TAG = "PicUtils";
    private static final String PNG = "png";
    private static final String JPG = "jpg";
    private static final String JPEG = "jpeg";
    private static final String BMP = "bmp";
    // private static final String[] imgSuffixes = {PNG, JPG, JPEG, BMP};
    private static final List<String> fileSuffixes = Arrays.asList(PNG, JPG, JPEG, BMP);
    // 快取檔案頭資訊-檔案頭資訊
    private static final ArrayMap<String, String> mFileTypes = new ArrayMap<String, String>();

    static {
        // images
        mFileTypes.put("FFD8FFE0", JPG);
        mFileTypes.put("89504E47", PNG);
        mFileTypes.put("424D5A52", BMP);
    }

    /**
     * 指定資料夾中的圖片檔案轉成JPG格式
     *
     * @param dir 圖片的所在資料夾路徑
     */
    public static void ImgToJPG(File dir) {
        File[] files = dir.listFiles();
        String filePath = "";
        for (int i = 0; i < files.length; i++) {
            //先通過後綴名,過濾非圖片
            String fileType = files[i].getName().substring(files[i].getName().lastIndexOf('.') + 1);
            if (fileSuffixes.contains(fileType.toLowerCase())) {
                filePath = files[i].getPath();
                String imgType = mFileTypes.get(getFileHeader(filePath)); //獲取真正的檔案頭
                if (!TextUtils.isEmpty(imgType) && !imgType.equals(JPG)) {
                    convertToJpg(filePath, filePath.substring(0, filePath.lastIndexOf('.') + 1) + JPG);
                }
            }
        }
    }

    /**
     * 轉換成JPG格式圖片 並將原照片刪除
     *
     * @param pngFilePath png或者bmp照片
     * @param jpgFilePath jpg照片
     */
    private static void convertToJpg(String pngFilePath, String jpgFilePath) {
        Bitmap bitmap = BitmapFactory.decodeFile(pngFilePath);
        try {
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(jpgFilePath));
            if (bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos)) {
                bos.flush();
            }
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            bitmap.recycle();
            bitmap = null;
        }
        //刪除非JPG照片
        if (!pngFilePath.equals(jpgFilePath)) {
            File oldImg = new File(pngFilePath);
            oldImg.delete();
        }
    }


    /**
     * 根據檔案路徑獲取檔案頭資訊
     *
     * @param filePath 檔案路徑
     * @return 檔案頭資訊
     */
    private static String getFileHeader(String filePath) {
        FileInputStream is = null;
        String value = null;
        try {
            is = new FileInputStream(filePath);
            byte[] b = new byte[4];
            is.read(b, 0, b.length);
            value = bytesToHexString(b);
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return value;
    }

    /**
     * 將要讀取檔案頭資訊的檔案的byte陣列轉換成string型別表示
     *
     * @param src 要讀取檔案頭資訊的檔案的byte陣列
     * @return 檔案頭資訊
     */
    private static String bytesToHexString(byte[] src) {
        StringBuilder builder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return "";
        }
        for (int i = 0; i < src.length; i++) {
            // 以十六進位制(基數 16)無符號整數形式返回一個整數引數的字串表示形式,並轉換為大寫
            String hv = Integer.toHexString(src[i] & 0xFF).toUpperCase();
            if (hv.length() < 2) {
                builder.append(0);
            }
            builder.append(hv);
        }
        return builder.toString();
    }
}