前言
本项目基于Google的Magenta平台,它采用随机森林分类器来识别图片的情感色彩,接着项目使用递归神经网络(RNN)来生成与图片情感相匹配的音乐,最后通过图形用户界面(GUI)实现可视化结果展示。
首先,项目处理图片,使用随机森林分类器来确定图片中的情感色彩。这可以包括情感分类,如欢快、宁静等。该分类器会分析图片的视觉特征,以确定其中蕴含的情感。
随后,根据图片中的情感,项目使用递归神经网络(RNN)生成与情感相匹配的音乐。这个过程涉及到选定特定音符、节奏和和声,以创造出与图片情感相一致的音乐作品。
最后,项目通过图形用户界面(GUI)将图片、情感色彩、生成的音乐等结果以可视化方式呈现给用户。
总之,这个项目结合了计算机视觉、音乐生成和图形用户界面设计,旨在将图片的情感色彩与音乐创作相融合,为用户提供一种独特的艺术体验。这对于艺术和技术的交叉应用可能非常引人注目。
总体设计
本部分包括系统整体结构图和系统流程图。
系统整体结构图
系统整体结构如图所示。
系统流程图
系统流程如图所示。
运行环境
本部分包括 Python 环境和Magenta环境。
Python环境
需要Python 3.6及以上配置,在Windows环境下推荐下载Anaconda完成Python所需环境的配置,下载地址为https://www.anaconda.com/,也可下载虚拟机在Linux环境下运行代码。
鼠标右击“我的电脑”,单击“属性”,选择高级系统设置。单击“环境变量”,找到系统变量中的Path,单击“编辑”然后新建,将Python解释器所在路径粘贴并确定。
Magenta环境
安装指南为:https://github.com/tensorflow/magenta/blob/master/README.md#installation
Magenta支持Python 2.7以上版本与Python 3.5以上版本。对于Linux用户,Magenta官方给出了安装脚本,使用方法如下:
curl https://raw.githubusercontent.com/tensorflow/magenta/master/magenta/tools/magenta-install.sh>/tmp/magenta-install.sh
bash /tmp/magenta-install.sh
上述脚本安装完成后,在source activate magenta
中打开Magenta运行环境。对于windows用户 (Linux用户也适用),也可使用Anaconda等包管理工具安装Magenta。
对于只使用CPU的用户:
conda create -n magenta python=3.6 #这里 3.5 也可以
activate magenta #(Linux 用户为 source activate magenta)
pip install magenta
Linux用户在安装Magenta之前需要先安装一些运行库,用以安装rimidi:
sudo apt-get install build-essential libasound2-dev libjack-dev
如果用到GPU,直接将上文中pip install magenta
替换成pip install magenta-gpu
。magenta 和magenta-gpu的区别仅在后者多了tensorfow-gpu库。
安装的TensorFlow版本为1.15.2。
模块实现
本项目包括3个模块:数据预处理、模型构建、模型训练及保存,下面分别给出各模块的功能介绍及相关代码。
1. 数据预处理
MIDI下载地址为http://midi.midicn.com/,图片在花瓣网收集获取地址为https://huaban.com/boards/60930738/。音乐模型包含欢快和安静两类MIDI文件各100个,图片包含欢快和安静两类各250张,格式为.jpg。另外,数据集也可以从本博客对应工程源码中下载。
(1)图片部分
提取图片中占比前十的色彩信息,将其转换成hsv格式,存储到.csv文件中,便于后续使用。
#-*- coding: utf-8 -*-
import sys
import csv
import traceback
import os
import pandas as pd
if sys.version_info < (3, 0):
from urllib2 import urlopen
else:
from urllib.request import urlopen
import io
from colorthief import ColorThief
def get_color(path):#获取图片的色彩信息
def rgb2hsv(tp):#将转换成hsv,h是色相,s是饱和度,v是明度
r,g,b=tp[0],tp[1],tp[2]
r, g, b = r/255.0, g/255.0, b/255.0
mx = max(r, g, b)
mn = min(r, g, b)
m = mx-mn
if mx == mn:
h = 0
elif mx == r:
if g >= b:
h = ((g-b)/m)*60
else:
h = ((g-b)/m)*60 + 360
elif mx == g:
h = ((b-r)/m)*60 + 120
elif mx == b:
h = ((r-g)/m)*60 + 240
if mx == 0:
s = 0
else:
s = m/mx
v = mx
h,s,v = round(h,3),round(s,3),round(v,3) #保留小数点后三位
h,s,v = str(h),str(s),str(v) #转成字符串类型能够写入csv
return h,s,v
fd = urlopen(path)
f = io.BytesIO(fd.read())
color_thief = ColorThief(f) #调用colortheif()函数
mc=color_thief.get_color(quality=1)
#获取画面最主要颜色,不一定出现在画面中,是整体均值,一个元组(r,g,b)
cp=color_thief.get_palette(quality=1) #获取调色盘
hsv = rgb2hsv(mc)
color_lists=[hsv] #用列表存储最主要颜色信息,[h,s,v]
clist=[color_lists[0][0],color_lists[0][1],color_lists[0][2]]
#获取最主要颜色的h,s,v值
for c in cp:#遍历调色盘中的颜色
hp=rgb2hsv(c)
color_lists.append(hp) #追加信息到列表中
for i in hp: #获取每个色彩的h,s,v值
clist.append(i)
return clist
def to_csv(clist): #将色彩信息列表存储到.csv文件
try:
fpath = 'E:/college/synaes/image_csv/2.csv'
#存储色彩信息的.csv文件地址
with open(fpath,'a',newline='')as f:
writer = csv.writer(f) #writer.writerow(["h0","s0","v0","h1","s1","v1","h2","s2","v2","h3","s3","v3","h4","s4","v4","h5","s5","v5","h6","s6","v6","h7","s7","v7","h8","s8","v8","h9","s9","v9","Label"])
writer.writerow(clist)
except:
print(traceback())
#get_color_to_file("file:///E:/college/synaes/image_test/test2.jpg")
path="E:/college/synaes/image/happy" #图片存储路径
file_list=os.listdir(path)
for file in file_list: #遍历路径中的每张图片
clist=get_color("file:///"+path+"/"+file)
to_csv(clist)
# -*- coding: utf-8 -*-
#从图像中抓取调色板
__version__ = '0.2.1'
import math
from PIL import Image #导入pillow中的image模块
class cached_property(object):
#创建的装饰器将单一参数的方法转换为缓存实例的属性
def __init__(self, func):
self.func = func
def __get__(self, instance, type):
res = instance.__dict__[self.func.__name__] = self.func(instance)
return res
class ColorThief(object):
#抓取调色的类
def __init__(self, file):
self.image = Image.open(file) #打开图像
def get_color(self, quality=10):
#获得主要的颜色,quality参数:1是最高的quality,数字越大,颜色返回越快
#返回tuple:(r, g, b)元组类型
palette = self.get_palette(5, quality)
return palette[0]
def get_palette(self, color_count=10, quality=10):
#获得调色盘,用中值切割算法聚类相似颜色
#参数color_count为调色盘的大小
#quality参数同上
#返回一个以(r,g,b)元组为元素的列表
image = self.image.convert('RGBA')
width, height = image.size
pixels = image.getdata() #像素
pixel_count = width * height
valid_pixels = []
for i in range(0, pixel_count, quality):
r, g, b, a = pixels[i]
#如果像素大部分是不透明的,而且不是白色的
if a >= 125:
if not (r > 250 and g > 250 and b > 250):
valid_pixels.append((r, g, b))
#将数组传给quantize()函数,该函数对值进行聚类,使用中值切割算法
cmap = MMCQ.quantize(valid_pixels, color_count)
return cmap.palette
class MMCQ(object):
#MMCQ基本是Python端口(改进的中值切割量化)
#算法来自Leptonica库(http://www.leptonica.com/)
SIGBITS = 5
RSHIFT = 8 - SIGBITS
MAX_ITERATION = 1000 #最大迭代次数
FRACT_BY_POPULATIONS = 0.75
@staticmethod
def get_color_index(r, g, b):
return (r << (2 * MMCQ.SIGBITS)) + (g << MMCQ.SIGBITS) + b
@staticmethod
def get_histo(pixels):
histo = dict()
for pixel in pixels:
rval = pixel[0] >> MMCQ.RSHIFT
gval = pixel[1] >> MMCQ.RSHIFT
bval = pixel[2] >> MMCQ.RSHIFT
index = MMCQ.get_color_index(rval, gval, bval)
histo[index] = histo.setdefault(index, 0) + 1
return histo
@staticmethod
def vbox_from_pixels(pixels, histo):
rmin = 1000000
rmax = 0
gmin = 1000000
gmax = 0
bmin = 1000000
bmax = 0
for pixel in pixels:
rval = pixel[0] >> MMCQ.RSHIFT
gval = pixel[1] >> MMCQ.RSHIFT
bval = pixel[2] >> MMCQ.RSHIFT
rmin = min(rval, rmin)
rmax = max(rval, rmax)
gmin = min(gval, gmin)
gmax = max(gval, gmax)
bmin = min(bval, bmin)
bmax = max(bval, bmax)
return VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo)
@staticmethod
def median_cut_apply(histo, vbox): #中值切割
if not vbox.count:
return (None, None)
rw = vbox.r2 - vbox.r1 + 1
gw = vbox.g2 - vbox.g1 + 1
bw = vbox.b2 - vbox.b1 + 1
maxw = max([rw, gw, bw])
#如果只有一个像素,不进行切割
if vbox.count == 1:
return (vbox.copy, None)
#沿着选定的轴查找数组
total = 0
sum_ = 0
partialsum = {}
lookaheadsum = {}
do_cut_color = None
if maxw == rw:
do_cut_color = 'r'
for i in range(vbox.r1, vbox.r2+1):
sum_ = 0
for j in range(vbox.g1, vbox.g2+1):
for k in range(vbox.b1, vbox.b2+1):
index = MMCQ.get_color_index(i, j, k)
sum_ += histo.get(index, 0)
total += sum_
partialsum[i] = total
elif maxw == gw:
do_cut_color = 'g'
for i in range(vbox.g1, vbox.g2+1):
sum_ = 0
for j in range(vbox.r1, vbox.r2+1):
for k in range(vbox.b1, vbox.b2+1):
index = MMCQ.get_color_index(j, i, k)
sum_ += histo.get(index, 0)
total += sum_
partialsum[i] = total
else: #maxw == bw
do_cut_color = 'b'
for i in range(vbox.b1, vbox.b2+1):
sum_ = 0
for j in range(vbox.r1, vbox.r2+1):
for k in range(vbox.g1, vbox.g2+1):
index = MMCQ.get_color_index(j, k, i)
sum_ += histo.get(index, 0)
total += sum_
partialsum[i] = total
for i, d in partialsum.items():
lookaheadsum[i] = total - d
#确定切割平面
dim1 = do_cut_color + '1'
dim2 = do_cut_color + '2'
dim1_val = getattr(vbox, dim1)
dim2_val = getattr(vbox, dim2)
for i in range(dim1_val, dim2_val+1):
if partialsum[i] > (total / 2):
vbox1 = vbox.copy
vbox2 = vbox.copy
left = i - dim1_val
right = dim2_val - i
if left <= right:
d2 = min([dim2_val - 1, int(i + right / 2)])
else:
d2 = max([dim1_val, int(i - 1 - left / 2)])
while not partialsum.get(d2, False):
d2 += 1
count2 = lookaheadsum.get(d2)
while not count2 and partialsum.get(d2-1, False):
d2 -= 1
count2 = lookaheadsum.get(d2)
#设置维度
setattr(vbox1, dim2, d2)
setattr(vbox2, dim1, getattr(vbox1, dim2) + 1)
return (vbox1, vbox2)
return (None, None)
@staticmethod
def quantize(pixels, max_color): #将颜色进行量化
#参数pixels是一个以(r,g,b)形式的像素列表
#参数max_color是颜色的最大数量
if not pixels:
raise Exception('Empty pixels when quantize.')
if max_color < 2 or max_color > 256:
raise Exception('Wrong number of max colors when quantize.')
histo = MMCQ.get_histo(pixels)
#检查是否低于maxcolors
if len(histo) <= max_color:
#从histo生成新的颜色并返回
pass
#从颜色重新获取起始vbox
vbox = MMCQ.vbox_from_pixels(pixels, histo)
pq = PQueue(lambda x: x.count)
pq.push(vbox)
#实现迭代的内部函数
def iter_(lh, target):
n_color = 1
n_iter = 0
while n_iter < MMCQ.MAX_ITERATION:
vbox = lh.pop()
if not vbox.count: #返回
lh.push(vbox)
n_iter += 1
continue
#实现切割
vbox1, vbox2 = MMCQ.median_cut_apply(histo, vbox)
if not vbox1:
raise Exception("vbox1 not defined; shouldn't happen!")
lh.push(vbox1)
if vbox2: #vbox2可以是null
lh.push(vbox2)
n_color += 1
if n_color >= target:
return
if n_iter > MMCQ.MAX_ITERATION:
return
n_iter += 1
#第一组颜色,按数量排序
iter_(pq, MMCQ.FRACT_BY_POPULATIONS * max_color)
#按像素占用率乘以色彩空间大小的乘积重新排序
pq2 = PQueue(lambda x: x.count * x.volume)
while pq.size():
pq2.push(pq.pop())
#下一组使用(npix * vol)排序生成中值切割
iter_(pq2, max_color - pq2.size())
#计算实际颜色
cmap = CMap()
while pq2.size():
cmap.push(pq2.pop())
return cmap
class VBox(object):
#3D颜色空间
def __init__(self, r1, r2, g1, g2, b1, b2, histo):
self.r1 = r1
self.r2 = r2
self.g1 = g1
self.g2 = g2
self.b1 = b1
self.b2 = b2
self.histo = histo
@cached_property
def volume(self):
sub_r = self.r2 - self.r1
sub_g = self.g2 - self.g1
sub_b = self.b2 - self.b1
return (sub_r + 1) * (sub_g + 1) * (sub_b + 1)
@property
def copy(self):
return VBox(self.r1, self.r2, self.g1, self.g2,
self.b1, self.b2, self.histo)
@cached_property
def avg(self):
ntot = 0
mult = 1 << (8 - MMCQ.SIGBITS)
r_sum = 0
g_sum = 0
b_sum = 0
for i in range(self.r1, self.r2 + 1):
for j in range(self.g1, self.g2 + 1):
for k in range(self.b1, self.b2 + 1):
histoindex = MMCQ.get_color_index(i, j, k)
hval = self.histo.get(histoindex, 0)
ntot += hval
r_sum += hval * (i + 0.5) * mult
g_sum += hval * (j + 0.5) * mult
b_sum += hval * (k + 0.5) * mult
if ntot:
r_avg = int(r_sum / ntot)
g_avg = int(g_sum / ntot)
b_avg = int(b_sum / ntot)
else:
r_avg = int(mult * (self.r1 + self.r2 + 1) / 2)
g_avg = int(mult * (self.g1 + self.g2 + 1) / 2)
b_avg = int(mult * (self.b1 + self.b2 + 1) / 2)
return r_avg, g_avg, b_avg
def contains(self, pixel):
rval = pixel[0] >> MMCQ.RSHIFT
gval = pixel[1] >> MMCQ.RSHIFT
bval = pixel[2] >> MMCQ.RSHIFT
return all([
rval >= self.r1,
rval <= self.r2,
gval >= self.g1,
gval <= self.g2,
bval >= self.b1,
bval <= self.b2,
])
@cached_property
def count(self):
npix = 0
for i in range(self.r1, self.r2 + 1):
for j in range(self.g1, self.g2 + 1):
for k in range(self.b1, self.b2 + 1):
index = MMCQ.get_color_index(i, j, k)
npix += self.histo.get(index, 0)
return npix
class CMap(object):
#颜色图
def __init__(self):
self.vboxes = PQueue(lambda x: x['vbox'].count * x['vbox'].volume)
@property
def palette(self):
return self.vboxes.map(lambda x: x['color'])
def push(self, vbox):
self.vboxes.push({
'vbox': vbox,
'color': vbox.avg,
})
def size(self):
return self.vboxes.size()
def nearest(self, color):
d1 = None
p_color = None
for i in range(self.vboxes.size()):
vbox = self.vboxes.peek(i)
d2 = math.sqrt(
math.pow(color[0] - vbox['color'][0], 2) +
math.pow(color[1] - vbox['color'][1], 2) +
math.pow(color[2] - vbox['color'][2], 2)
)
if d1 is None or d2 < d1:
d1 = d2
p_color = vbox['color']
return p_color
def map(self, color):
for i in range(self.vboxes.size()):
vbox = self.vboxes.peek(i)
if vbox['vbox'].contains(color):
return vbox['color']
return self.nearest(color)
class PQueue(object):
#简单优先级队列
def __init__(self, sort_key):
self.sort_key = sort_key
self.contents = []
self._sorted = False
def sort(self):
self.contents.sort(key=self.sort_key)
self._sorted = True
def push(self, o):
self.contents.append(o)
self._sorted = False
def peek(self, index=None):
if not self._sorted:
self.sort()
if index is None:
index = len(self.contents) - 1
return self.contents[index]
def pop(self):
if not self._sorted:
self.sort()
return self.contents.pop()
def size(self):
return len(self.contents)
def map(self, f):
return list(map(f, self.contents))
相关其它博客
基于随机森林+RNN+Tensorflow-Magenta的根据图片情感智能生成音乐系统——深度学习算法应用(含python、ipynb工程源码)+所有数据集(二)
基于随机森林+RNN+Tensorflow-Magenta的根据图片情感智能生成音乐系统——深度学习算法应用(含python、ipynb工程源码)+所有数据集(三)
基于随机森林+RNN+Tensorflow-Magenta的根据图片情感智能生成音乐系统——深度学习算法应用(含python、ipynb工程源码)+所有数据集(四)
工程源代码下载
其它资料下载
如果大家想继续了解人工智能相关学习路线和知识体系,欢迎大家翻阅我的另外一篇博客《重磅 | 完备的人工智能AI 学习——基础知识学习路线,所有资料免关注免套路直接网盘下载》
这篇博客参考了Github知名开源平台,AI技术平台以及相关领域专家:Datawhale,ApacheCN,AI有道和黄海广博士等约有近100G相关资料,希望能帮助到所有小伙伴们。