图像拟合
1 遗传算法简介
遗传算法是一种迭代优化算法,每次对**个体(individuals)组成的种群(Population)**进行优化,其中每个个体对应一个问题的可行解。
1.1 适应度函数
每个个体都由适应度函数(Fitness function)来进行评价,一般而言,适应度越好的个体能更好地解决问题。
算法根据个体的适应度(fitness)进行选择,将一些个体指定为双亲,由双亲产生子代个体(offspring)形成新的一代。
- 新一代和上一代的大小形式应该相同,但是我们只保留新一代而删除上一代,即迭代。
- 适应度越好的个体产生子代的概率越大。
- 子代的属性是由其双亲组合而来的,具体组成方式多种多样。
若适应度函数设计良好,种群就会更快更准的收敛于最优解。
1.2 简单遗传算法(SGA)
以下是SGA的算法表示:
BEGIN
Generate initial population;生成初代种群
Compute fitness of each individual; 计算每个个体(染色体)的适应度
REPEAT 重度以下操作来生成新的种群
Selection 选择要成为双亲的个体,鼓励进化,降低多样性,一般选取适应度较高的个体进行
Crossover 双亲交叉,生成子代(这个类似于繁殖)
Mutation 变异,子代的某些属性进行了一些变化,有一定的变异率(孩子不可能和父母一模一样)
Evaluate new popilation 评估新的种群
UNTIL population has converge
END
1.3 主要问题
- 如何表示个体?(需要进行编码和解码)
- 如何初始化种群?(随机化方法)
- 如何进行选择?(设置适应度函数)
- 如何进行交叉(繁殖)?
- 什么时候停止算法?
1.4 编码和解码
定义
将问题结构变换为位串形式编码表示的过程叫编码(Encoding);相反将位串形式编码表示变换为原问题结构的过程叫解码(Decoding)。
通常将解对应的参数(基因)连接成位串以构成染色体(个体)。
本质上所有的字符都可以用编码染色体,但是通常我们采用二进制来进行编码。
编码方式
二进制编码,实数编码,排列编码,规则列表等
基因的排列顺序通常很重要,好的编码方式对GA的性能有非常重要的影响。
二进制编码
- 离散型表示
对于二类问题可以直接使用二进制,在一个位串中的个体正例记为1,个体反例记为0;对于多类问题可以用整数记录,方法同上,但是会潜在给类别定义顺序。 - 实数表示
可以用二进制转化为十进制来表示实数 - 浮点数表示
可以用二进制来表示某一区间内的浮点数。
二进制解码
解码过程即编码的逆过程,采用逆向思维解决便可。
其他编码方式简述
- 实数编码:一组实数,可以将向量映射为向量中的一个元素。
- 基于序列的表示:
1.5 个体评估
如上文所述,每个个体都对应一个适应度,例如在TSP问题中的适应度即为路途的代价。选择一个正确的适应度函数十分重要,但是也非常困难。通常我们是要最大化或者最小化适应度函数。
1.6 选择策略
选择操作也是GA中非常重要的一环,但是也是非常耗时的一环。选择主要是为了保证较优的个体有更高的繁殖机会,是种群进化的主要驱动力。但是要注意,给予次优个体一定概率的繁殖机会能增加种群的多样性,有利于产生有用的基因。
常用的选择策略有:轮盘赌法;锦标赛法。
轮赌法
选择任意一个个体i的概率为
f
i
/
∑
f
j
f_i/\sum{f_j}
fi/∑fj
其中fi为i的适应度,由此可以看出,更好的个体,即适应度较高的个体会有更大的空间,也就是更多机会被选中。
具体算法:
- 计算所有染色体的适应度之和T以及运行和(即当前个体与之前所有个体的适应度之和)
- 生成0-T之间的随机数N
- 返回运行和大于等于N的第一个染色体
因此,选择概率于适应度成正比
缺点:
- 优势个体占据整个种群的速度非常快,容易导致早熟,陷入局部最优
- 适应度值非常接近时,选择压力变低
锦标赛法
一般而言,锦标赛指从种群中随机抽取k个个体进行比较,选取k个个体中最好的一个,k为锦标赛的规模
二值锦标赛:随机选择两个个体,适应度高的个体被选中为双亲染色体
概率二值锦标赛:随机选择两个个体,以概率p来选择适应度高的
大规模锦标赛:随机选择n个个体,适应度最高的个体被选中
1.7 繁殖
繁殖包括交叉和变异
交叉
两个双亲染色体产生两个子代个体,有一定的概率两个双亲染色体直接成为子代,也有一定概率双亲染色体随机组合生成子代
包括:单点交叉,两点交叉,均匀交叉,算术交叉
交叉操作与编码方式相关,简单的交叉可能导致非法子代,例如TSP中交叉产生的子代路径可能完全不通,通常可以使用均匀交叉来回避这个问题,。实数编码也可以使用均匀交叉或者算术交叉。
变异
子代个体以一定的概率随机改变自己的基因,一般而言变异率会很低。变异也是生成子代的过程,主导个体的多样性。注意,交叉之恶能对现有基因吃进行重组,而变异可以生成新的基因。变异程度是很重要的,应该慎重设置,我们是希望生成有效的染色体。
对二进制编码的变异,可以随机选取一个值进行改变。
对实数编码的变异,通过添加随机噪声,如高斯噪声。
x
’
i
=
x
i
+
N
(
0
,
σ
)
x’_i=x_i+N(0,\sigma)
x’i=xi+N(0,σ)
交换变异:任意选取位序中的两个值进行交换。
2 实验内容
### 2.1 实验要求
本次实验的目的是完成图像拟合代码的调试,并撰写实验运行报告。报告应为Word文档,包含以下内容:
- 染色体编码方式:说明所使用的染色体编码方式,以及编码方式的具体实现。
- 选择策略
- 交叉策略
- 变异策略
- 实验结果
2.2 实验内容
本实验旨在通过遗传算法实现图像拟合,具体内容如下:
-
阅读示例代码并理解其实现思路,确定项目的编程形式。
-
确定使用的染色体编码方式,每个染色体由多个三角形构成,第i个三角形的特征向量
x i = ( x i 1 , x i 2 , x i 3 , x i 4 , x i 5 , x i 6 , x i 7 , x i 8 , x i 9 , x i 10 ) x_i = (x_{i1},x_{i2},x_{i3},x_{i4},x_{i5},x_{i6},x_{i7},x_{i8},x_{i9},x_{i10}) xi=(xi1,xi2,xi3,xi4,xi5,xi6,xi7,xi8,xi9,xi10)
前六个元素为三角形三个角的坐标,即(ax,ay),(bx,by),(cx,cy),后四个元素为三角形的演示与透明度,即(r, g, b, alpha).
-
确定选择策略,个体适应度为当前个体像素差的平方,值越小适应度越高。
-
确定交叉策略,将两个种中的某些三角形进行交换。
-
确定变异策略,个体中部分三角形进行变异。
-
运行遗传算法程序,并记录初代、中间代和最终结果的最差与最优个体图像,并标明对应的进化代数。
绘制最差、最优和平均适应度值随进化代数变化的收敛曲线图,其中横轴为进化代数,纵轴为适应度值。图中应有3条曲线分别对应每一代最差、最优和平均适应度值的变化。
-
对实验结果进行分析和讨论,评价所使用的遗传算法在图像拟合方面的性能和优缺点,并提出改进方案。
2.3 编程示例
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from PIL import Image, ImageDraw
import os
import gc
import random as r
import minpy.numpy as np
class Color(object):
'''
定义颜色的类,这个类包含r,g,b,a表示颜色属性
'''
def __init__(self):
self.r = r.randint(0, 255)
self.g = r.randint(0, 255)
self.b = r.randint(0, 255)
self.a = r.randint(95, 115)
def mutate_or_not(rate):
'''
生成随机数,判断是否需要变异
'''
return True if rate > r.random() else False
class Triangle(object):
'''
定义三角形的类
属性:
ax,ay,bx,by,cx,cy:表示每个三角形三个顶点的坐标
color : 表示三角形的颜色
img_t : 三角形绘制成的图,用于合成图片
方法:
mutate_from(self, parent): 从父代三角形变异
draw_it(self, size=(256, 256)): 绘制三角形
'''
max_mutate_rate = 0.08
mid_mutate_rate = 0.3
min_mutate_rate = 0.8
def __init__(self, size=(255, 255)):
self.ax = r.randint(0, size[0])
self.ay = r.randint(0, size[1])
self.bx = r.randint(0, size[0])
self.by = r.randint(0, size[1])
self.cx = r.randint(0, size[0])
self.cy = r.randint(0, size[1])
self.color = Color()
self.img_t = None
def mutate_from(self, parent):
if mutate_or_not(self.max_mutate_rate):
self.ax = r.randint(0, 255)
self.ay = r.randint(0, 255)
if mutate_or_not(self.mid_mutate_rate):
self.ax = min(max(0, parent.ax + r.randint(-15, 15)), 255)
self.ay = min(max(0, parent.ay + r.randint(-15, 15)), 255)
if mutate_or_not(self.min_mutate_rate):
self.ax = min(max(0, parent.ax + r.randint(-3, 3)), 255)
self.ay = min(max(0, parent.ay + r.randint(-3, 3)), 255)
if mutate_or_not(self.max_mutate_rate):
self.bx = r.randint(0, 255)
self.by = r.randint(0, 255)
if mutate_or_not(self.mid_mutate_rate):
self.bx = min(max(0, parent.bx + r.randint(-15, 15)), 255)
self.by = min(max(0, parent.by + r.randint(-15, 15)), 255)
if mutate_or_not(self.min_mutate_rate):
self.bx = min(max(0, parent.bx + r.randint(-3, 3)), 255)
self.by = min(max(0, parent.by + r.randint(-3, 3)), 255)
if mutate_or_not(self.max_mutate_rate):
self.cx = r.randint(0, 255)
self.cy = r.randint(0, 255)
if mutate_or_not(self.mid_mutate_rate):
self.cx = min(max(0, parent.cx + r.randint(-15, 15)), 255)
self.cy = min(max(0, parent.cy + r.randint(-15, 15)), 255)
if mutate_or_not(self.min_mutate_rate):
self.cx = min(max(0, parent.cx + r.randint(-3, 3)), 255)
self.cy = min(max(0, parent.cy + r.randint(-3, 3)), 255)
# color
if mutate_or_not(self.max_mutate_rate):
self.color.r = r.randint(0, 255)
if mutate_or_not(self.mid_mutate_rate):
self.color.r = min(max(0, parent.color.r + r.randint(-30, 30)), 255)
if mutate_or_not(self.min_mutate_rate):
self.color.r = min(max(0, parent.color.r + r.randint(-10, 10)), 255)
if mutate_or_not(self.max_mutate_rate):
self.color.g = r.randint(0, 255)
if mutate_or_not(self.mid_mutate_rate):
self.color.g = min(max(0, parent.color.g + r.randint(-30, 30)), 255)
if mutate_or_not(self.min_mutate_rate):
self.color.g = min(max(0, parent.color.g + r.randint(-10, 10)), 255)
if mutate_or_not(self.max_mutate_rate):
self.color.b = r.randint(0, 255)
if mutate_or_not(self.mid_mutate_rate):
self.color.b = min(max(0, parent.color.b + r.randint(-30, 30)), 255)
if mutate_or_not(self.min_mutate_rate):
self.color.b = min(max(0, parent.color.b + r.randint(-10, 10)), 255)
# alpha
if mutate_or_not(self.mid_mutate_rate):
self.color.a = r.randint(95, 115)
# if mutate_or_not(self.mid_mutate_rate):
# self.color.a = min(max(0, parent.color.a + r.randint(-30, 30)), 255)
# if mutate_or_not(self.min_mutate_rate):
# self.color.a = min(max(0, parent.color.a + r.randint(-10, 10)), 255)
def draw_it(self, size=(256, 256)):
self.img_t = Image.new('RGBA', size)
draw = ImageDraw.Draw(self.img_t)
draw.polygon([(self.ax, self.ay),
(self.bx, self.by),
(self.cx, self.cy)],
fill=(self.color.r, self.color.g, self.color.b, self.color.a))
return self.img_t
class Canvas(object):
'''
定义每一张图片的类
属性:
mutate_rate : 变异概率
size : 图片大小
target_pixels: 目标图片像素值
方法:
add_triangles(self, num=1) : 在图片类中生成num个三角形
mutate_from_parent(self, parent): 从父代图片对象进行变异
calc_match_rate(self) : 计算环境适应度
draw_it(self, i) : 保存图片
'''
mutate_rate = 0.01
size = (256, 256)
target_pixels = []
def __init__(self):
self.triangles = []
self.match_rate = 0
self.img = None
def add_triangles(self, num=1):
for i in range(0, num):
triangle = Triangle()
self.triangles.append(triangle)
def mutate_from_parent(self, parent):
flag = False
for triangle in parent.triangles:
t = triangle
if mutate_or_not(self.mutate_rate):
flag = True
a = Triangle()
a.mutate_from(t)
self.triangles.append(a)
continue
self.triangles.append(t)
if not flag:
self.triangles.pop()
t = parent.triangles[r.randint(0, len(parent.triangles) - 1)]
a = Triangle()
a.mutate_from(t)
self.triangles.append(a)
def calc_match_rate(self):
if self.match_rate > 0:
return self.match_rate
self.match_rate = 0
self.img = Image.new('RGBA', self.size)
draw = ImageDraw.Draw(self.img)
draw.polygon([(0, 0), (0, 255), (255, 255), (255, 0)], fill=(255, 255, 255, 255))
for triangle in self.triangles:
self.img = Image.alpha_composite(self.img, triangle.img_t or triangle.draw_it(self.size))
# 与下方代码功能相同,此版本便于理解但效率低
# pixels = [self.img.getpixel((x, y)) for x in range(0, self.size[0], 2) for y in range(0, self.size[1], 2)]
# for i in range(0, min(len(pixels), len(self.target_pixels))):
# delta_red = pixels[i][0] - self.target_pixels[i][0]
# delta_green = pixels[i][1] - self.target_pixels[i][1]
# delta_blue = pixels[i][2] - self.target_pixels[i][2]
# self.match_rate += delta_red * delta_red + \
# delta_green * delta_green + \
# delta_blue * delta_blue
arrs = [np.array(x) for x in list(self.img.split())] # 分解为RGBA四通道
for i in range(3): # 对RGB通道三个矩阵分别与目标图片相应通道作差取平方加和评估相似度
self.match_rate += np.sum(np.square(arrs[i]-self.target_pixels[i]))[0]
def draw_it(self, i):
#self.img.save(os.path.join(PATH, "%s_%d_%d_%d.png" % (PREFIX, len(self.triangles), i, self.match_rate)))
self.img.save(os.path.join(PATH, "%d.png" % (i)))
def main():
global LOOP, PREFIX, PATH, TARGET, TRIANGLE_NUM
# 声明全局变量
img = Image.open(TARGET).resize((256, 256)).convert('RGBA')
size = (256, 256)
Canvas.target_pixels = [np.array(x) for x in list(img.split())]
# 生成一系列的图片作为父本,选择其中最好的一个进行遗传
parentList = []
for i in range(20):
print('正在生成第%d个初代个体' % (i))
parentList.append(Canvas())
parentList[i].add_triangles(TRIANGLE_NUM)
parentList[i].calc_match_rate()
parent = sorted(parentList, key=lambda x: x.match_rate)[0]
del parentList
gc.collect()
# 进入遗传算法的循环
i = 0
while i < 30000:
childList = []
# 每一代从父代中变异出10个个体
for j in range(10):
childList.append(Canvas())
childList[j].mutate_from_parent(parent)
childList[j].calc_match_rate()
child = sorted(childList, key=lambda x: x.match_rate)[0]
# 选择其中适应度最好的一个个体
del childList
gc.collect()
parent.calc_match_rate()
if i % LOOP == 0:
print ('%10d parent rate %11d \t child1 rate %11d' % (i, parent.match_rate, child.match_rate))
parent = parent if parent.match_rate < child.match_rate else child
# 如果子代比父代更适应环境,那么子代成为新的父代
# 否则保持原样
child = None
if i % LOOP == 0:
# 每隔LOOP代保存一次图片
parent.draw_it(i)
#print(parent.match_rate)
#print ('%10d parent rate %11d \t child1 rate %11d' % (i, parent.match_rate, child.match_rate))
i += 1
'''
定义全局变量,获取待处理的图片名
'''
NAME = input('请输入原图片文件名:')
LOOP = 100
PREFIX = NAME.split('/')[-1].split('.')[0] # 取文件名
PATH = os.path.abspath('.') # 取当前路径
PATH = os.path.join(PATH,'results')
TARGET = NAME # 源图片文件名
TRIANGLE_NUM = 256 # 三角形个数
if __name__ == '__main__':
#print('开始进行遗传算法')
main()