不想看廢話的直接跳到實現部分
引言
原本以爲網上會有很多這方面的工具類可以實現,但是發現寥寥無幾,而且大部分還都是其他的語言的。
最近在研究人臉的識別技術,大部分的人臉識別開源庫都是使用pgm的圖片格式作爲識別圖片格式。
因此測試和使用時,必然要把jpg等彩色圖片轉爲pgm的灰度圖片格式使用。
在講方法之前,先進行一下圖片的格式科普:
PPM(Portable PixMap)是portable像素圖片,是由netpbm項目定義的一系列的portable圖片格式中的一個。這些圖片格式都相對比較容易處理,跟平臺無關,所以稱之爲portable,就是比較直接的圖片格式。
比如PPM,其實就是把每一個點的RGB分別保存起來。
所以,PPM格式的文件是沒有壓縮的,相對比較大,但是由於圖片格式簡單,一般作爲圖片處理的中間文件(不會丟失文件信息),或者作爲簡單的圖片格式保存。
netpbm的幾種圖片格式是通過其表示的顏色類型來區別的,PBM是單色圖,只有黑色和白色,PGM是灰度圖片,PPM是代表完整的RGB顏色的圖片,有時也統稱爲PNM格式。
科普1
那麼接下來單純講 PGM的文件存儲信息格式:
PGM
該格式文件存儲灰度圖形,也就是這裏每個像素使用一個值來表示而不是3個(R,G,B)。
同PPM唯一不同的是頭部用P2和P5,分別表示用ASCII和字節碼來表示數據。
例如 :
這是一張PGM格式圖片的形式
第一行 P5
720 1280 是圖片尺寸
255 代表文件數據部分可能出現的像素灰度值的最大值,後面的便是圖片的內容數據 。
科普2
灰度圖是如何計算的生成的:
灰度圖直觀地講就是將原來的RGB圖像轉換爲只有灰度級的圖像,做這一步處理也比較簡單,只要把每個像素點的RGB值拿出來,算一下他們的平均值(R+G+B)/3,然後再替換原來的RGB值就OK了。
科普3
在使用Java SE中類來讀取圖片時,通常使用ImageIO包中的類來讀取圖片,但是有個缺點就是默認支持的圖片格式很少,Java 8中javax.imageio支持的圖片格式有:
從圖中看出庫僅僅支持JPEG、PNG、BMP、WBMP以及GIF這些圖片格式。如果想讀取其他格式時應該怎麼辦?網上搜索到了一個Java ImageIO的擴展插件——TwelveMonkeys,安裝後ImageIO就可以支持多種圖片格式,這個有一個好處就是原先讀取圖片的代碼不需要改變,讀取圖片還是使用 javax.imageio 包。
ps: imageio-pnm裏面有一個大坑 那就是在轉成pgm的時候 它生成的文件頭 是P6
但是在這裏我直接用原生的字節流讀取寫入,因此不用理會 TwelveMonkeys
只是在讀取jpg png 圖片格式的時候需要用到 imageIo
實現
在項目: https://gist.github.com/armanbilge/3276d80030d1caa2ed7c#file-pgmio-java-L2 的基礎上進行了完善
完善的項目演示代碼:
https://gitee.com/Keith404/PGMIO
package utlis;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* A utility class for reading and writing PGM images. Methods use integers to represent unsigned bytes.
* <p>
* Does not fully conform to the PGM specification because currently there is no support for:
* <ul>
* <li>More than one image per file</li>
* <li>Images with more than 256 shades of gray</li>
* <li>Comments within the raster</li>
* </ul>
* github https://gist.github.com/armanbilge/3276d80030d1caa2ed7c#file-pgmio-java-L2
*
* @author Arman Bilge
*/
public final class PgmIo {
private static final String MAGIC = "P5";
/**
* Character indicating a comment.
*/
private static final char COMMENT = '#';
/**
* The maximum gray value.
*/
private static final int MAXVAL = 255;
/**
* Reads a grayscale image from a file in PGM format.
*
* @param file the PGM file read from
* @return two-dimensional byte array representation of the image
* @throws IOException
*/
public static int[][] read(final File file) throws IOException {
final BufferedInputStream stream = new BufferedInputStream(new FileInputStream(file));
try {
if (!next(stream).equals(MAGIC))
throw new IOException("File " + file + " is not a binary PGM image.");
final int col = Integer.parseInt(next(stream));
final int row = Integer.parseInt(next(stream));
final int max = Integer.parseInt(next(stream));
if (max < 0 || max > MAXVAL)
throw new IOException("The image's maximum gray value must be in range [0, " + MAXVAL + "].");
final int[][] image = new int[row][col];
for (int i = 0; i < row; ++i) {
for (int j = 0; j < col; ++j) {
final int p = stream.read();
if (p == -1)
throw new IOException("Reached end-of-file prematurely.");
else if (p < 0 || p > max)
throw new IOException("Pixel value " + p + " outside of range [0, " + max + "].");
image[i][j] = p;
}
}
return image;
} finally {
stream.close();
}
}
/**
* Finds the next whitespace-delimited string in a stream, ignoring any comments.
*
* @param stream the stream read from
* @return the next whitespace-delimited string
* @throws IOException
*/
private static String next(final InputStream stream) throws IOException {
final List<Byte> bytes = new ArrayList<Byte>();
while (true) {
final int b = stream.read();
if (b != -1) {
final char c = (char) b;
if (c == COMMENT) {
int d;
do {
d = stream.read();
} while (d != -1 && d != '\n' && d != '\r');
} else if (!Character.isWhitespace(c)) {
bytes.add((byte) b);
} else if (bytes.size() > 0) {
break;
}
} else {
break;
}
}
final byte[] bytesArray = new byte[bytes.size()];
for (int i = 0; i < bytesArray.length; ++i)
bytesArray[i] = bytes.get(i);
return new String(bytesArray);
}
/**
* Writes a grayscale image to a file in PGM format.
*
* @param image a two-dimensional byte array representation of the image
* @param file the file to write to
* @throws IllegalArgumentException
* @throws IOException
*/
public static void write(final int[][] image, final File file) throws IOException {
write(image, file, MAXVAL);
}
public static void write(BufferedImage image, final File file) throws IOException {
int[][] rgbArray = new int[image.getHeight()][image.getWidth()];
for (int j = 0; j < image.getHeight(); j++) {
for (int i = 0; i < image.getWidth(); i++) {
int p = image.getRGB(i, j);
int a = (p >> 24) & 0xff;
int r = (p >> 16) & 0xff;
int g = (p >> 8) & 0xff;
int b = p & 0xff;
int avg = (r + g + b) / 3;
p = (a << 24) | (avg << 16) | (avg << 8) | avg;
//newImg.setRGB(i, j, p);
//image.setRGB(i, j, p);
rgbArray[j][i] = avg;
}
}
write(rgbArray, file);
}
public static void write(File imageFile, final File file) throws IOException {
try {
BufferedImage bf = ImageIO.read(imageFile);
write(bf, file);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Writes a grayscale image to a file in PGM format.
*
* @param image a two-dimensional byte array representation of the image
* @param file the file to write to
* @param maxval the maximum gray value
* @throws IllegalArgumentException
* @throws IOException
*/
public static void write(final int[][] image, final File file, final int maxval) throws IOException {
if (maxval > MAXVAL)
throw new IllegalArgumentException("The maximum gray value cannot exceed " + MAXVAL + ".");
final BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(file));
try {
stream.write(MAGIC.getBytes());
stream.write("\n".getBytes());
stream.write(Integer.toString(image[0].length).getBytes());
stream.write(" ".getBytes());
stream.write(Integer.toString(image.length).getBytes());
stream.write("\n".getBytes());
stream.write(Integer.toString(maxval).getBytes());
stream.write("\n".getBytes());
for (int i = 0; i < image.length; i++) {
for (int j = 0; j < image[0].length; j++) {
final int p = image[i][j];
if (p < 0 || p > maxval)
throw new IOException("Pixel value " + p + " outside of range [0, " + maxval + "].");
stream.write(image[i][j]);
}
}
//you can delete println
System.out.println("PGM create successful, file name :" + file.getName() + "\nwidth:" + image[0].length + ",height:" + image.length);
} finally {
stream.close();
}
}
}
測試使用:
實現了重載write方法,可以傳入文件,也可以傳入一個圖片流。
第一個參數爲 需要轉換的源圖片文件,第二個參數爲 輸出的文件。
import org.junit.Test;
import utlis.PgmIo;
import java.io.File;
import java.io.IOException;
/**
* @author: create by Keith
* @version: v1.0
* @description: PACKAGE_NAME
* @date:2020/5/28
**/
public class PgmIoTest {
@Test
public void pgmWriteTest() {
try {
PgmIo.write(new File("image/sa1.jpg"), new File("image/sa1.pgm"));
} catch (IOException e) {
e.printStackTrace();
}
}
}