清风的技术小屋

不为所动, 做更专业的自己

0%

最近下载了一个人脸数据库,里面的图片都是pgm格式,想提取每张图片的所有像素的像素值,初步设想是使用Java SE中读取图片。在使用Java SE中类来读取图片时,通常使用ImageIO包中的类来读取图片,但是有个缺点就是默认支持的图片格式很少,Java 8中javax.imageio支持的图片格式有:

从图中看出库仅仅支持JPEG、PNG、BMP、WBMP以及GIF这些图片格式。如果想读取其他格式(例如PGM)时应该怎么办?网上搜索到了一个Java ImageIO的扩展插件——TwelveMonkeys,安装后ImageIO就可以支持多种图片格式,这个有一个好处就是原先读取图片的代码不需要改变,读取图片还是使用 javax.imageio 包。

安装TwelveMonkeys

安装很简单,使用Maven可以很容易地添加到项目中,如果想让ImageIO支持JPEG和TIFF格式,可以在POM文件中添加下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
<dependencies>
...
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>3.3.1</version> <!-- Alternatively, build your own version -->
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>3.3.1</version> <!-- Alternatively, build your own version -->
</dependency>
</dependencies>

如果要支持PGM格式,可以添加下面代码:

1
2
3
4
5
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-pnm</artifactId>
<version>3.3.1</version>
</dependency>

想要支持什么格式唯一要变的地方就是artifactId中内容,基本模版就是imageio-xxx

读取PGM图片

读取一张PGM图片,然后将图片的所有像素的像素值输出到一个文本文件,实现代码如下:

灰度图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package Image;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.*;

public class PGM {
public static void main(String[] args) throws IOException {
// Grayscale image
String path = "/Users/luowanqian/Downloads/Zach_Pillar_0001.pgm";
String output = "/Users/luowanqian/Downloads/output.txt";

BufferedImage image = ImageIO.read(new File(path));
Raster source = image.getRaster();
int width = image.getWidth();
int height = image.getHeight();

int[] pixels = new int[width*height];
source.getPixels(0, 0, width, height, pixels);

BufferedWriter out = new BufferedWriter(new FileWriter(output));
for (int i=0; i<width*height; i++)
out.write(pixels[i] + " ");
out.close();
}
}

Yale人脸数据库在人脸识别论文中频繁用到,因此做人脸识别这方面研究还是存一份这个数据库以备用。在网上可以很容易下载这个数据库,我在浙大蔡登教授的主页上下载了这个数据库(Four face databases in matlab format),主页上这个数据库有两种大小可以选择下载,一个是图像大小是32x32的数据库,另一个是64x64,本文选择的是32x32。

数据集说明

主页上有对这个数据集说明:

Contains 165 grayscale images in GIF format of 15 individuals. There are 11 images per subject, one per different facial expression or configuration: center-light, w/glasses, happy, left-light, w/no glasses, normal, right-light, sad, sleepy, surprised, and wink.

显示人脸图像

主页上提供显示数据库中人脸图像的代码,整理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
% load data
load('Yale_32x32.mat');

faceW = 32;
faceH = 32;
numPerLine = 11;
ShowLine = 2;

Y = zeros(faceH*ShowLine, faceW*numPerLine);
for i=0:ShowLine-1
for j=0:numPerLine-1
Y(i*faceH+1:(i+1)*faceH, j*faceW+1:(j+1)*faceW) = reshape(fea(i*numPerLine+j+1,:), [faceH, faceW]);
end
end

imagesc(Y);
colormap(gray);

pause();

导出数据到文本文件

为了后续使用,我将人脸数据导出到文本文件中,文本每行包含了一张人脸数据以及该人脸对应的类标,写了一个简短的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
clear all; close all; clc;

% Load data
% Contains variables 'fea' and 'gnd'
% Each row of 'fea' is a face; 'gnd' is the label
load('Yale_32x32.mat');

output = 'data.txt';
[row, col] = size(fea);

file = fopen(output, 'w');
for i=1:row
for j=1:col
fprintf(file, '%d ', fea(i,j));
end
fprintf(file, '%d\n', gnd(i));
end

fclose(file);

代码运行后生成一个文本文件 data.txt

关于 Hadoop Streaming 的使用,可以参考官方网站 Hadoop Streaming。个人觉得使用Steaming这个功能,可以使用其他语言来编写MapReduce程序,相比使用Java来编写程序,工作量小了很多。

根据《Hadoop权威指南》中例子使用python来实现 Max Temperature 这个程序,要分别实现Map函数以及Reduce函数。

Map函数

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python

import re
import sys

for line in sys.stdin:
val = line.strip()
(year, temp, q) = (val[15:19], val[87:92], val[92:93])
if (temp != "+9999" and re.match("[01459]", q)):
print "%s\t%s" % (year, temp)

Reduce函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python

import sys

(last_key, max_val) = (None, -sys.maxint)
for line in sys.stdin:
(key, val) = line.strip().split("\t")
if last_key and last_key != key:
print "%s\t%s" % (last_key, max_val)
(last_key, max_val) = (key, int(val))
else:
(last_key, max_val) = (key, max(max_val, int(val)))

if last_key:
print "%s\t%s" % (last_key, max_val)

本文用到的文件如下:

其中 1901.txt为数据文件,max_temperature_map.py定义了Map函数,max_temperature_reduce.py定义了Reduce函数

定义好Map函数以及Reduce函数后,利用unix的管道将Map过程以及Reduce过程连接起来,使用下面命令

1
$ cat 1901.txt | ./max_temperature_map.py | sort | ./max_temperature_reduce.py

运行结果如下:

运行结果正确,此时就可以使用Hadoop的Streaming了,使用下面命令

1
2
3
4
5
6
$ hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-2.7.3.jar \
-fs file:/// \
-input 1901.txt \
-output output \
-mapper max_temperature_map.py \
-reducer max_temperature_reduce.py

注意:如果文件是在本地文件系统中而不是HDFS中时,要使用 -fs file:/// 这个命令选项

运行结果保存到 output 文件夹中

在实验《Hadoop权威指南 第3版》书本上的例子4-2时发现了一个关于 FileSystem 的问题。例子代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.apache.hadoop.io.compress.CompressionCodecFactory;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;

public class FileDecompressor {
public static void main(String[] args) throws Exception {
String uri = args[0];
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(URI.create(uri), conf);

Path inputPath = new Path(uri);
CompressionCodecFactory factory = new CompressionCodecFactory(conf);
CompressionCodec codec = factory.getCodec(inputPath);
if (codec == null) {
System.err.println("No codec found for " + uri);
System.exit(1);
}

String outputUri =
CompressionCodecFactory.removeSuffix(uri, codec.getDefaultExtension());

InputStream in = null;
OutputStream out = null;
try {
in = codec.createInputStream(fs.open(inputPath));
out = fs.create(new Path(outputUri));
IOUtils.copyBytes(in, out, conf);
} finally {
IOUtils.closeStream(in);
IOUtils.closeStream(out);
}
}
}

编译好按照使用书本上的命令行运行程序

1
% hadoop FileDecompressor file.gz

运行时抛出了 java.io.FileNotFoundException 异常

错误说找不到文件 /user/luowanqian/file.gz,但是我的 file.gz 并不在这个目录里面,而是在 /home/luowanqian/Downloads/Temp/MaxTemperature,如果使用绝对路径作为参数传递给程序也会报错,最终在绝对路径前面加上 file:// 后程序就没有报错了。

初步分析了这个问题,个人认为是程序默认在HDFS中寻找 file.gz,而 file.gz 是在本地文件系统中,因此程序要识别这个文件是在本地文件系统中,需要在路径前面添加 file://,为了进一步验证我的猜想,我修改了原来的代码。

1
FileSystem fs = FileSystem.get(URI.create(uri), conf);

修改成

1
FileSystem fs = FileSystem.getLocal(conf);

再次使用下面命令运行程序

1
% hadoop FileDecompressor file.gz

这个时候程序就正常运行了

至于程序如何判断文件是在怎么样的文件系统目前还不知道怎么解决,初学Hadoop还是很多地方不清楚。

最近在学习Hadoop时候遇到了一个问题,就是操作Hadoop文件系统时遇到 Connection Refused 问题,当时问题显示如下:

遇到这问题时,首先查看了错误信息提示中提到的网址 https://wiki.apache.org/hadoop/ConnectionRefused,文章中提示说是端口问题,但是经过排查不是端口问题,所以只能排查其他原因了。首先使用Java的 jps 命令查看JVM,查看结果如下:

发现没有NameNode,然后查看了Hadoop关于NameNode日志(日志文件默认在Hadoop安装目录中的 logs 目录下),发现了一些东西:

日志说 /tmp/hadoop-luowanqian/dfs/name 目录不存在,这个目录是HDFS文件系统的镜像文件保存的地方,HDFS文件系统格式化后其镜像文件默认保存在 tmp 目录中,而这个目录中的文件在重启系统后会自动被删除,因此访问HDFS文件系统会失败,重新格式化HDFS文件系统后再访问文件系统正常了。