一、前言
以前大学时候,学EXCEL看到N多大神利用excel画图,觉得很不可思议。今个学了一个来月python,膨胀了就想用excel画图。当然,其实用画图这个词不甚严谨,实际上是利用opencv遍历每一个像素的rgb值,再将其转化为16进制,最后调用openpyxl进行填充即可。
1.1、实现效果
效果如下图
-[-/a>
1.2、需要用到的库的安装
需要用到库如下:
?
1
2
3
4
5
|
import cv2
import xlsxwriter
import openpyxl
import numpy as np
import pandas as pd
|
除了第一个库其他的可以直接用pip在命令提示行进行安装,或者利用编辑器的一些自动安装功能也非常的方便,具体的请参看这篇文章 的第三节: 三、开始安装
第一个库如果你直接用pip3 install opencv-python 进行安装的话,无论你网速多么快,都会非常慢几k/s,如下:
-[-/a>
如果能安装好还行,关键有的可能等上几分钟也不行,直接出现几十行的红色字看的头疼。几经百度后才知道是安装源的问题,切换为国内的安装源即可,利用如下命令,
?
1
|
pip3 install - i https: / / pypi.tuna.tsinghua.edu.cn / simple opencv - python
|
如下图,我准备截取安装速度和上面的作对比的,结果直接安装好了
-[-/a>
二、代码分开讲解
本文我们利用面相对象的编程思维进行。
2.1、对象的定义以及初始化
?
1
2
3
4
|
class ImageToExcel():
def __init__( self ,image_path,excel_path):
self .imgviewx = cv2.imread(image_path,cv2.IMREAD_COLOR)
self .excel_path = excel_path
|
前面两行很好理解就是定义对象的格式以及初始化对象
其中image_path和excel_path 这两个变量是你的图像储存路径和后续的excel文件保存位置。
第三行self.imgviewx=cv2.imread(image_path,cv2.IMREAD_COLOR) 意思是调用opencv的imread读取图片。其中第一个参数就是对象实例化时候传递进来的图像储存路径。该函数返回的是一个三维数组,分别表示x,y,rgb 就是x,y坐标对应的rgb值,其中x,y单位为1像素。最后将这个三维数组传递给对象的一个属性imgviewx,等待后续对象方法调用。我们将之打印出来如下。
-[-/a>
第四行<<self.excel_path=excel_path >>是将对象实例化时候传递进来的excel_path传递给对象的属性excel_path,同样等待后续对象的方法调用。
2.2、对象的方法
1:行高列宽调整,以防止图像变形
?
1
2
3
4
5
6
7
|
def excel_size( self ):
workbook = xlsxwriter.Workbook( self .excel_path)
worksheet = workbook.add_worksheet( 'test' )
worksheet.set_column( 'A:CAA' , 1 )
for x in range ( 2000 ):worksheet.set_row(x, 8.4 )
workbook.close()
|
这个其实你可以后续在excel中调整也可以。
第二行第三行基本一看就懂,就是在你刚开始对象实例化时候传入的一个路径中创建一个工作簿并添加一个名为test的工作表。
第三行意思是将A列到CAA列的列宽设置为1(注意:这里面设置为1不知道为什么在工作表中就是0.94,列宽同样小点)
第四行意思同样,不过行高不能批量只能通过循环。
最后一样看着像关闭,其实最主要功能是保存,没有这一行,前面的所有设置都不会被保存。
2.3、对象的方法2:10进制转化为16进制
?
1
2
3
4
|
def ten2_16( self ,num):
num1 = hex (num).replace( '0x' , '')
return num1 if len (num1) > 1 else '0' + num1
|
这个方法不用细说,就是利用系统自带的函数hex将10进制转化为16进制。我们都知道hex返回的16进制是以0x开头的,而16进制颜色码中明显没有,所以要用replace去掉。
如果rgb值是16以内的,以16进制显示的话会是1位数,而同样这个在16进制颜色码中也没有,所以最后一行的意思就是一位数的话在开头补0。
2.4、对象的方法3:获取r、g、b值并运用方法1转化为16进制颜色码
?
1
2
3
4
5
6
7
|
def get_rgb_data( self ):
self .excel_size()
data_r = pd.DataFrame( np.array( self .imgviewx)[:,:, 2 ] ).applymap( self .ten2_16)
data_g = pd.DataFrame( np.array( self .imgviewx)[:,:, 1 ] ).applymap( self .ten2_16)
data_b = pd.DataFrame( np.array( self .imgviewx)[:,:, 0 ] ).applymap( self .ten2_16)
return (data_r + data_g + data_b).values
|
其中第二行<<self.excel_size() >>是在本方法本调用时候先调用方法1调整行高列宽。我们后面说,这关系到对象方法之间的参数传递,我们后续说。
三四五行的代码结构一样,我们挑一个说。比如第三行data_r=pd.DataFrame( np.array(self.imgviewx)[:,:,2] ).applymap(self.ten2_16) 这个代码我们可以拆开成下面的代码
?
1
2
3
|
r = np.array( self .imgviewx)[:,:, 2 ]
tmp = pd.DataFrame( r )
data_r = tmp.applymap( self .ten2_16)
|
这下就容易懂了
第一行意思是将刚开始对象初始化时候得到的包含目标图片的所有像素点的rgb值的三维列表转化为数组并提取其中的r。
第二行是将第一行得到的数组转化为DataFrame对象并存储在tmp变量中,以便第三行的处理。
第三行是利用DataFrame中的applymap将r值转化为16进制。
最后一行<<return (data_r+data_g+data_b).values >>意思是将转化为16进制的rgb值合并后就得到了16进制的颜色码并转化为数组。
2.5、对象的方法4;颜色填充
?
1
2
3
4
5
6
7
8
9
|
def color_fill( self ):
rgb_list = self .get_rgb_data()
wb = openpyxl.load_workbook( self .excel_path)
ws = wb[ 'test' ]
for x,tmp1 in list ( enumerate (rgb_list)):
print ( '总共有%s行,已填充%s行,还剩下%s行' % ( len (rgb_list),x + 1 , len (rgb_list) - x - 1 ))
for y ,tmp2 in list ( enumerate (tmp1)):
ws.cell(x + 1 ,y + 1 ).fill = openpyxl.styles.fills.GradientFill(stop = [ str (tmp2), str (tmp2)])
wb.save( self .excel_path)
|
第二行<<rgb_list=self.get_rgb_data() >>是不是似曾相识,对,就是在方法2中调用方法1时候用的。
这里就是在本方法也就是方法3中调用方法2。唯一的区别就是有没有返回值。
我们这样在方法3中调用方法2然后方法2中调用方法1。这样在对象外的时候我们就只用对象实例化并调用方法3即可实现功能。
第三行、第四行就是调用openpyxl.load_workbook打开我们在方法1中新建的工作簿中的test工作表
五到七行两个循环嵌套很容易懂就是利用循环遍历每个工作表
第八行的代码可能可以简化 ,这个是我修改网上的一个填充渐变色的代码。
最后一行就是工作表的保存,没什么可说的。
三、完整代码
?
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
41
42
43
|
import cv2
import xlsxwriter
import openpyxl
import numpy as np
import pandas as pd
class ImageToExcel():
def __init__( self ,image_path,excel_path):
self .imgviewx = cv2.imread(image_path,cv2.IMREAD_COLOR)
self .excel_path = excel_path
def excel_size( self ):
workbook = xlsxwriter.Workbook( self .excel_path)
worksheet = workbook.add_worksheet( 'test' )
worksheet.set_column( 'A:CAA' , 1 )
for x in range ( 2000 ): worksheet.set_row(x, 8.4 )
workbook.close()
def ten2_16( self ,num):
tmp = hex (num).replace( '0x' , '')
return tmp if len (tmp) > 1 else '0' + tmp
def get_rgb_data( self ):
self .excel_size()
data_r = pd.DataFrame( np.array( self .imgviewx)[:,:, 2 ] ).applymap( self .ten2_16)
data_g = pd.DataFrame( np.array( self .imgviewx)[:,:, 1 ] ).applymap( self .ten2_16)
data_b = pd.DataFrame( np.array( self .imgviewx)[:,:, 0 ] ).applymap( self .ten2_16)
return (data_r + data_g + data_b).values
def color_fill( self ):
rgb_list = self .get_rgb_data()
wb = openpyxl.load_workbook( self .excel_path)
ws = wb[ 'test' ]
for x,tmp1 in list ( enumerate (rgb_list)):
print ( '总共有%s行,已填充%s行,还剩下%s行' % ( len (rgb_list),x + 1 , len (rgb_list) - x - 1 ))
for y ,tmp2 in list ( enumerate (tmp1)):
ws.cell(x + 1 ,y + 1 ).fill = openpyxl.styles.fills.GradientFill(stop = [ str (tmp2), str (tmp2)])
wb.save( self .excel_path)
excel_path = 'test23.xlsx'
image_path = 'tttt.png'
image = ImageToExcel(image_path,excel_path)
image.color_fill()
|
最后四行前两行可以直接写在第三行中,就是对象的实例化中
另外还有一点,image_path中的 tttt.jpg是直接和我的py文件放在一起的,不然运行会报错。
四、结语
好啦,到此所有东西已全部搞定,当然还有很多要注意的,
第一、方法3中红绿蓝的提取中这部分的编号是刚好相反的,提取时候需要注意
如下
-[-/a>
当然你也可以尝试改变这个值看最后会得到什么结果。蓝色的太阳 红色的天空 或者是绿色的帽子,哈哈 好吧这个就你们自己发挥了。
第二、除了以上一点需要注意的,还有一个需要注意,就是像素不能太高。我测试了下342*218的话我的i7-6700u打开excel就不是很流畅了。
你可以遍历的时候以2个像素点或者四个像素点为步长,不过这样我没试过,可能颗粒感比较明显吧(自己猜测没试过),或者把原始图片修改下。
|