RGB色彩模式是工业界的一种颜色标准,是通过对红®、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是运用最广的颜色系统之一。
将一张图片的一个像素点的rgb值作为一个基因,那么整张图片所有像素点就能组成一个基因序列,基于此基因序列通过遗传算法不断的迭代就能将一张完全乱序的图片重组为想要的图片。
例如目标图片为:
第0代图片:
第558代图片:
第1000代图片
遗传算法(GeneticAlgorithm)是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,通过模拟自然进化过程搜索最优解。遗传算法是从首先是初始化一个种群,然后根据适应性函数确定个体的适应度,由适应度来选择个体进行交叉,以某种概率让个体进行变异,从而不断选出适应度高的个体,进而更新种群。
实现遗传算法重现图片,分为以下几个步骤:
读取目标图片->产生初代种群->计算适应度->遗传算法->保存图片->画出适应度迭代曲线
适应度计算方法:
#计算适应度并存入gene[1]
#适应度计算方法:
#1、定义像素点差异度:一个像素点的r、g、b与目标像素点对应r、g、b的差值的绝对值求和之后取平均数,
# 即:a = (|r1-r2|+|g1-g2|+|b1-b2|)/3
#2、将所有像素点的差异度求和取平均后作为适应度
# 即:1/N * ∑ai
#3、适应度越小,图片与目标图片重合越好
遗传算法流程:
# 遗传算法
# 1、确定种群规模,迭代次数,变异概率等
# 2、初始化种群
# 3、计算个体适应度
# 4、选择保留的个体
# 5、选择个体进行交叉
# 6、选择个体进行变异
# 7、用产生的新个体更新种群
# 8、如果达到迭代次数则输出适应度最高的个体,否则回到第(3)步
# 9、结束
交叉:
#随机挑选种群中一半的个体与另一半个体交叉
#随机生成一个范围,表示交叉的基因片段,称该片段为中段
#在parent1中取中段加入chirden1,再将parent2中前段和后段的其他像素点按顺序加入chirden1
#在parent2中取中段加入chirden2,再将parent1中前段和后段的其他像素点按顺序加入chirden2
变异:
# 变异(每个像素点的r/g/b值都有mutation_probability的机率发生变异)
#确定变异像素点个数,变异像素点个数为当前种群最小适应度的1~5倍,这样随着适应度的减小,发生变异的像素也会减小。
读取目标图片的所有像素,将每个像素封装成一个列表[r,g,b],列表中有三个元素,分别是该像素对应的r,g,b值,将所有像素按列读取,并存入color列表中
#读取图像获取基因
def __init__(self):
print("读取源图像!")
img = Image.open('./image/source_image/lingyuner.png')
self.color = []
self.img_size = img.size
width, height = img.size
#注意这里的color是一个行向量,原来图片中的一列像素变成了color列表中的一行,每一列都向列表后追加
for j in range(height):
for i in range(width):
r,g,b = img.getpixel((i,j))[:3]
self.color.append([r,g,b])
print("源图像读取完毕!大小{}".format(self.img_size))
#随机生成100个初代图片
def rand_genes(self):
print("开始初始化种群!")
width,height = self.img_size
#种群基因
genes = []
for i in range(100):
#个体基因
gene = []
for j in range(height):
for k in range(width):
r = random.randint(0,255)
g = random.randint(0,255)
b = random.randint(0,255)
gene.append([r,g,b])
genes.append([gene,255])#255为默认适应度
#计算个体适应度
self.fitness(genes[-1])
print("种群初始化完毕!")
return genes
#print(genes[99][0][0])#一张图片中的一行像素
#print(genes[99][0][1])#基因适应度
#计算适应度并存入gene[1]
#适应度计算方法:
#1、定义像素点差异度:一个像素点的r、g、b与目标像素点对应r、g、b的差值的绝对值求和之后取平均数,
# 即:a = (|r1-r2|+|g1-g2|+|b1-b2|)/3
#2、将所有像素点的差异度求和取平均后作为适应度
# 即:1/N * ∑ai
#3、适应度越小,图片于源图片重合越好
def fitness(self,gene):
sum = 0
for i in range(self.img_size[0]*self.img_size[1]):
r1,g1,b1 = gene[0][i]
r2,g2,b2 = self.color[i]
a = (abs(r1-r2)+abs(g1-g2)+abs(b1-b2))/3
sum += a
gene[1] = sum/(self.img_size[1]*self.img_size[0])
def take_last(self,elem):
return elem[-1]
# 遗传算法
# 1、确定种群规模,迭代次数,变异概率等
# 2、初始化种群
# 3、计算个体适应度
# 4、选择保留的个体
# 5、选择个体进行交叉
# 6、选择个体进行变异
# 7、用产生的新个体更新种群
# 8、如果达到迭代次数则输出适应度最高的个体,否则回到第(3)步
# 9、结束
def genetic_algorithm(self):
# 确定种群规模,迭代次数,变异概率等
population_size = 100
generations = 10
mutation_probability = 0.1
x = [] # 迭代次数
y = [] # 每轮迭代中最优子代对应的适应度
# 初始化种群
genes = self.rand_genes()
a = 0
while a < generations:
a += 1
#4、计算个体适应度
for i in range(population_size,len(genes)):
self.fitness(genes[i])
#将种群按适应度从小到大排列
genes.sort(key=self.take_last)
#选择前population_size个体保留
genes = genes[:population_size]
#输出最小适应度,并把该子代保存为图片
print(genes[0][1])
x.append(a)
y.append(genes[0][1])
self.save_pic(genes[0],a)
# 5、选择个体进行交叉
#随机挑选种群中一半的个体与另一半个体交叉
parent1_list = []
parent2_list = []
for i in range(int(population_size/2)):
tmp = random.randint(0, population_size - 1)
#如果tmp已经存在,继续随机
while tmp in parent1_list:
tmp = random.randint(0, population_size - 1)
parent1_list.append(tmp)
for i in range(population_size):
if i not in parent1_list:
parent2_list.append(i)
for i in range(int(population_size/2)):
children1 = []
children2 = []
pre_index = [] #前段
mid_index = [] #中段
last_index = [] #后段
parent1 = genes[parent1_list[i]][0]
parent2 = genes[parent2_list[i]][0]
#在0~(87*100-1)中随机一个片段
mid_index.append(random.randint(0,self.img_size[0] * self.img_size[1] - 1))
mid_index.append(random.randint(0, self.img_size[0] * self.img_size[1] - 1))
while mid_index[0] == mid_index[1]:
mid_index[1] = random.randint(0, self.img_size[0] * self.img_size[1] - 1)
#从小到大排序
mid_index.sort()
#根据中段的范围推出前段
pre_index = [0,mid_index[0]]
#根据中段的范围推出后段
last_index = [mid_index[1],self.img_size[0] * self.img_size[1]]
#在parent1中取中段加入chirden1,再将parent2中前段和后段的其他像素点按顺序加入chirden1
#切片是浅拷贝,使用deepcopy进行深拷贝)
children1 = deepcopy(parent2[pre_index[0]:pre_index[1]]) + deepcopy(parent1[mid_index[0]:mid_index[1]]) + deepcopy(parent2[last_index[0]:last_index[1]])
# 在parent2中取中段加入chirden2,再将parent1中前段和后段的其他像素点按顺序加入chirden2
children2 = deepcopy(parent1[pre_index[0]:pre_index[1]]) + deepcopy(parent2[mid_index[0]:mid_index[1]]) + deepcopy(parent1[last_index[0]:last_index[1]])
# 变异(每个像素点的r/g/b值都有mutation_probability的机率发生变异)
#确定变异像素点个数,变异像素点个数为当前种群最小适应度的1~5倍,这样随着适应度的减小,发生变异的像素也会减小。
num = int(genes[0][1] * 1.5)
index = []
#随机num个像素点发生变异
for j in range(num):
temp = random.randint(0,self.img_size[0] * self.img_size[1] - 1)
while temp in index:
temp = random.randint(0, self.img_size[0] * self.img_size[1] - 1)
index.append(temp)
for j in range(num):
for k in range(3):
if random.randint(1, 100) <= 100 * mutation_probability:
#print("像素点发生变异!")
#print(children1[index[j]])
rgb = random.randint(0, 255)
children1[index[j]][k] = rgb
#print(children1[index[j]])
if random.randint(1, 100) <= 100 * mutation_probability:
rgb = random.randint(0, 255)
children2[index[j]][k] = rgb
genes.append([children1,255])
genes.append([children2,255])
if a == 100 or a == 500 or a == 1000 or a == 5000 or a == 100000:
final = time.time()
# 输出最终适应度
print("{}轮适应度:{}".format(a,genes[0][1]))
print("花费时间 {} s".format(final-begin))
# 最优子代迭代图
plt.title("genetic_algorithm")
plt.xlabel("generations")
plt.ylabel("fitness")
plt.scatter(x, y, 1, )
# 连接各个点
for i in range(len(x) - 1):
start = (x[i], x[i + 1])
end = (y[i], y[i + 1])
plt.plot(start, end, color='#000000', linewidth=0.5)
plt.show()
#保存图片
def save_pic(self,gene,generation):
gene = gene[0]
img = Image.open('./image/reproduction_picture/lingyuner.png')
j = -1
i = 0
#gene是由8700个像素点组成,要把该基因转成100*87的图片,需要每一百个基因存为一列,一共87列
for lenth in range(self.img_size[0]*self.img_size[1]):
if i == 0:
j += 1
r,g,b = gene[lenth]
img.putpixel((i,j),(r,g,b))
i = (i + 1) % self.img_size[0]
img.save("./image/reproduction_picture/{}.png".format(generation))
print("第{}代保存成功!".format(generation))
在使用遗传算法重现图片的过程中,变异的像素点数量以及发生变异的概率都尤为重要,这决定着整个算法的执行效率和最终结果。
例如在最开始的时候使用的是下面的变异代码,对于一个基因序列中的每个像素点的三个r,g,b数字都判断是否变异,假设变异概率为10%,每一代会产生100个新的子代,每个子代有8700个基因,基因中还有三个r,g,b数字,那么在迭代5000次时就有100 * 8700 * 3 * 5000 * 0.1 = 1,305,000,000,也就是13亿次变异,变异次数太多导致5000轮迭代在服务器上跑了一晚上也没有得到理想的结果 。
for j in range(self.img_size[0]*self.img_size[1]):
for k in range(3):
if random.randint(1,100) <= 100 * mutation_probability:
#print("像素点发生变异!")
rgb = random.randint(0,255)
children1[j][k] = rgb
if random.randint(1,100) <= 100 * mutation_probability:
#print("像素点发生变异!")
rgb = random.randint(0,255)
children2[j][k] = rgb
未改进前5000轮效果图:
于是结合yanxxizao博主的思路对变异代码进行改进。
# 变异(每个像素点的r/g/b值都有mutation_probability的机率发生变异)
#确定变异像素点个数,变异像素点个数为当前种群最小适应度的1~5倍,这样随着适应度的减小,发生变异的像素也会减小。
num = int(genes[0][1] * 1.5)
index = []
#随机num个像素点发生变异
for j in range(num):
temp = random.randint(0,87 * 100 - 1)
while temp in index:
temp = random.randint(0, 87 * 100 - 1)
index.append(temp)
for j in range(num):
for k in range(3):
if random.randint(1, 100) <= 100 * mutation_probability:
#print("像素点发生变异!")
#print(children1[index[j]])
rgb = random.randint(0, 255)
children1[index[j]][k] = rgb
#print(children1[index[j]])
if random.randint(1, 100) <= 100 * mutation_probability:
rgb = random.randint(0, 255)
children2[index[j]][k] = rgb
改进后1000轮效果图:
python 人工智能学习 遗传算法实现图片再现