☻Blog("Laziji")

System.out.print("辣子鸡的博客");

在传统编程中, 图像识别一直是一个难点, 虽然人能轻松做到, 但是用逻辑来描述这个过程, 并转换成程序是很难的。机器学习的出现让图像识别技术有了突破性的进展, 卷积神经网络的出现, 又使图像识别更上了一次层次。

卷积神经网络由一个或多个卷积层和顶端的全连通层组成, 这一结构使得卷积神经网络能够利用输入数据的二维结构。与其他深度学习结构相比,卷积神经网络在图像和语音识别方面能够给出更好的结果。

这里我们使用卷积神经网络对人脸进行性别识别, 项目中使用了TensorFlow机器学习库。

项目地址

face-gender-classification

数据收集与处理

机器学习的基础就是大量的数据。我以前从网上爬了一万张证件照, 现在正好用上, 作为训练数据。
简便的也可以从谷歌直接搜搜索 男(女)性证件照也可以得到并且有标签的数据。
由于我收集的照片没有标签, 于是我花了一点时间从其中人工选出男女照片各200张并打上标记。

为了使识别更加准确, 项目中利用openCV裁剪出人脸部分的图像, 并缩放至28*28大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
recognizer = cv2.CascadeClassifier("model/haarcascade_frontalface_default.xml")
crop(img_path):
try:
img = cv2.imread(img_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = recognizer.detectMultiScale(gray)
if len(faces):
x, y, w, h = faces[0]
c_img = img[y:y + h, x:x + w]
return cv2.resize(c_img, (28, 28), interpolation=cv2.INTER_AREA)
except:
pass

return None

对所有的数据都进行这样处理, 结果如下:
1
最后我们还需要清理异常的数据, 过一遍训练集, 把其中没有定位到人脸的图片去除掉。

训练模型

读取训练数据。

1
2
3
4
5
6
7
8
9
10
11
12
def read_img(files):
arr = []
for file in files:
img = Image.open("%s" % file)
pix = img.load()
view = np.zeros((IMAGE_H, IMAGE_W, 1), dtype=np.float)
for x in range(IMAGE_H):
for y in range(IMAGE_W):
r, g, b = pix[y, x]
view[x, y, 0] = (r + g + b) // 3
arr.append(view)
return np.array(arr)

这里对训练图像灰度化, 并且将训练数据中的一小部分作为验证集。

开始创建模型。

1
2
3
4
5
6
7
8
9
10
model = keras.Sequential([
keras.layers.Conv2D(32, (3, 3), input_shape=(IMAGE_W, IMAGE_H, 1), strides=(1, 1), activation='relu'),
keras.layers.MaxPool2D(pool_size=(2, 2)),
keras.layers.Conv2D(64, (3, 3), strides=(1, 1), activation='relu'),
keras.layers.MaxPool2D(pool_size=(2, 2)),
keras.layers.Flatten(),
keras.layers.Dense(128, activation=tf.nn.relu),
keras.layers.Dropout(0.2),
keras.layers.Dense(2, activation=tf.nn.softmax)
])

选择适当的优化器和损失函数编译模型。

1
2
3
model.compile(optimizer=tf.train.AdamOptimizer(learning_rate=0.001),
loss='categorical_crossentropy',
metrics=['accuracy'])

开始训练模型。

1
2
3
4
5
6
7
8
9
model.fit(x=train_x,
y=train_y,
batch_size=32,
epochs=30,
verbose=1,
callbacks=my_callbacks,
validation_split=0.05,
shuffle=True
)

测试模型

这里使用matplotlib来显示测试图片及结果。

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
predictions = model.predict(test_x)

class_names = ["Female", "Male"]

plt.figure(figsize=(12, 6))
for i in range(min(9, len(test_y))):
result = predictions[i]
max_label = int(np.argmax(result))
correct_label = int(np.argmax(test_y[i]))

plt.subplot(3, 6, 2 * i + 1)
plt.grid(False)
plt.xticks([])
plt.yticks([])
img = test_x.reshape(test_x.shape[0], IMAGE_W, IMAGE_H)[i]
plt.imshow(img, cmap="gray")
plt.xlabel("{} - prob:{:2.0f}%".format(class_names[max_label], 100 * np.max(result)))

plt.subplot(3, 6, 2 * i + 2)
plt.grid(False)
plt.yticks([])
plt.ylim([0, 1])
bar = plt.bar(range(2), result)
bar[max_label].set_color('red')
bar[correct_label].set_color('green')

plt.show()

2
脸部头像右侧的两列分别代表女性概率男性概率
这里我们看到全都对了, 正确率非常高。
模型并不复杂, 大部分工作都在收集数据和调整训练参数上, 这也体现出了卷积神经网络对图像强大的处理能力。

本文介绍如何用Java编写高度自定义的代码生成器

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息。

上面这一段话来自Mybatis官网的介绍, 初用Mybatis时感觉这个框架相比于JDBC优雅多了, 用起来也如官网说的非常简单。但是用了一段时间之后, 弊端就慢慢凸显出来了

使用Mybatis时不得不为每个表创建一个Entity.javaMapper.xml(Mapper可以融合入Dao中)Dao.java,Service.java 层次很清晰, 但是太多重复性的工作了, 费时间且易于出错

并且当数据库发生一点改动的时候… 苦不堪言

后来出现了自动生成代码的插件, 但是总是不尽人意, 不能随心所欲地控制, 毕竟每个人的需求都不一样

本文就来介绍如何简单的编写一个自己的代码生成器

项目源码

mybatis-generator

代码实现

实现的思路很简单, 首先查询数据库的表结构, 得到列名, 列类型...等信息

创建文件模版, 将这些信息插入模版中, 最后打包模版进压缩包导出

代码实现 一共五个Java类

  • TableDO
  • ColumnDO
  • GeneratorMapper
  • GeneratorUtils
  • GeneratorService

首先来看两个实体类

TableDO 和 ColumnDO

TableDO 存放表名, 对于的类名, 以及列信息

完整类代码 TableDO.java

1
2
3
4
5
6
7
8
9
public class TableDO {

private String tableName;
private List<ColumnDO> columns;
private String className;
private String suffix;

// get()... set()...
}

ColumnDO 存放列名, 数据库字段类型, 以及对应Java中的属性名和类型

完整类代码 ColumnDO.java

1
2
3
4
5
6
7
8
9
10
public class ColumnDO {

private String columnName;
private String dataType;
private String attrName;
private String attrLowerName;
private String attrType;

// get()... set()...
}

GeneratorMapper

在GeneratorMapper 中, 我们通过表名查询表字段的信息

完整类代码 GeneratorMapper.java

1
2
3
4
5
6
@Mapper
public interface GeneratorMapper {

@Select("select column_name columnName, data_type dataType from information_schema.columns where table_name = #{tableName} and table_schema = (select database()) order by ordinal_position")
List<ColumnDO> listColumns(String tableName);
}

GeneratorUtils

在GeneratorUtils 中进行类信息与模版之间的转换

完整类代码 GeneratorUtils.java

将表信息放入Velocity模版的上下文中

1
2
3
4
5
6
7
8
9
10
11
12
Map<String, Object> map = new HashMap<>();
map.put("tableName", table.getTableName());
map.put("className", table.getClassName());
map.put("pathName", getPackageName().substring(getPackageName().lastIndexOf(".") + 1));
map.put("columns", table.getColumns());
map.put("package", getPackageName());
map.put("suffix", table.getSuffix());

Properties prop = new Properties();
prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
Velocity.init(prop);
VelocityContext context = new VelocityContext(map);

添加模版

1
2
3
4
5
6
List<String> templates = new ArrayList<>();
templates.add("mybatis/Model.java.vm");
templates.add("mybatis/Query.java.vm");
templates.add("mybatis/Dao.java.vm");
templates.add("mybatis/Mapper.xml.vm");
templates.add("mybatis/Service.java.vm");

编译模版

1
2
3
StringWriter sw = new StringWriter();
Template tpl = Velocity.getTemplate(template, "UTF-8");
tpl.merge(context, sw);

Utils类完成了生成代码的主要工作, 但是代码也是比较简单的

GeneratorService

在Service 中注入Mapper 查询列信息, 并用Utils生成代码, 然后导出压缩包

完整类代码 GeneratorService.java

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
@Service
public class GeneratorService {

@Resource
private GeneratorMapper generatorMapper;

@Resource
private Environment environment;

public void generateZip(String[] tableNames, String zipPath) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(outputStream);
for (String tableName : tableNames) {
TableDO table = new TableDO();
table.setTableName(tableName);
table.setColumns(generatorMapper.listColumns(tableName));
GeneratorUtils.generatorCode(table, zip,getConfig());
}
IOUtils.closeQuietly(zip);
FileOutputStream file = new FileOutputStream(zipPath);
file.write(outputStream.toByteArray());
file.close();
}

// getConfig ...
}

VM模版

自己写代码生成器的好处就是, 可以根据需求定制自己的模版, 下面是我的几个模版可以供参考

  • Mapper.xml.vm
  • Dao.java.vm
  • Service.java.vm
  • Model.java.vm
  • Query.java.vm

生成的代码是在commons-mybatis架构下使用的

Dao.java.vm

1
2
3
4
5
6
7
8
9
10
11
package ${package}.database.dao;

import ${package}.database.model.${className}${suffix};

import org.apache.ibatis.annotations.Mapper;
import org.laziji.commons.mybatis.dao.${suffix}Dao;

@Mapper
public interface ${className}Dao extends ${suffix}Dao<${className}${suffix}> {

}

其余模版

使用

配置文件

resources下创建application-${name}.yml文件, ${name}随意, 例如: application-example.yml, 可创建多个

配置文件内容如下, 填入数据库配置, 以及生成代码的包名, 源文件路径

1
2
3
4
5
6
7
8
9
spring:
datasource:
url: jdbc:mysql://xxx.xxx.xxx.xxx:3306/xxxx?characterEncoding=utf-8
username: xxxxxx
password: xxxxxx

generator:
package: com.xxx.xxx
resources: mapper

Test

在test文件下创建测试类

  • @ActiveProfiles("example")中填入刚才配置文件名的name
  • tableNames需要生成的表, 可以多个
  • zipPath 代码导出路径
    运行测试方法即可
    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
    package pg.laziji.generator;

    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.ActiveProfiles;
    import org.springframework.test.context.junit4.SpringRunner;
    import pg.laziji.generator.mybatis.GeneratorService;

    import javax.annotation.Resource;
    import java.io.IOException;

    @ActiveProfiles("example")
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class ExampleTest {

    @Resource
    private GeneratorService generatorService;

    @Test
    public void test() throws IOException {
    String[] tableNames = new String[]{"example_table1", "example_table2"};
    String zipPath = "/home/code.zip";
    generatorService.generateZip(tableNames,zipPath);
    }
    }

0%