最近一直在忙,好久没有上博客园更新文章。趁着有空,将近期用python实现的一个小工具发布出来。这个是本人在实际的工作当中,为了提高效率而开发的一个小工具。正所谓"麻雀虽小,五脏俱全”,虽然是一个简单的小工具,也包含了很多的知识点在里面,也当是这段时间接触python的一次小结吧。与此同时,后期还会不断的完善,该版本只是一个雏形,仅仅实现了最基本的功能,还有很多的地方不是很完善,所以后期会不断的更新。
需求:
本人从事camera影像调试的工作,在高通平台下tuning手机摄像头模组。在实际的工作当中,发现有很多拍摄RAW图的时间比较繁琐,而且很多是重复的操作,特别是客观调试的时候,场景基本上都是固定了,但是我们每次去拍照的时候,需要在一个场景下拍图,然后把RAW图导出来,同时更改文件名。很多老练的工程师,都已经习惯了,一个一个的拍,然后改名,放到不同的文件夹下面,中间又需要不断地敲命令导出raw图。可想而知,如果项目的周期很赶,那么使用手动的方式,效率上会浪费比较多的时间,而且容易乱。本人又比较懒,同时又发现了这其中有很多操作是重复的,所以就打算自己写一个工具,来实现大部分的工作,这样就可以节省了比较多的时间,用于真正的分析重要问题上面。
需求分析:
既然我们已经知道了要做什么事情,就需要不断地去需求摸清楚,然后选择合适的工具去实现。由于实际的工作又比较简单,用脚本的方式是最容易思实现的,并且不需要编译啥的,即使换一台机器,在相同的操作系统下面仍然能用,省去了很多的麻烦。最开始是使用脚本的方式,比较简单,代码很少,如下:

@echo off rem 在运行该脚本前,请先打开手机的骁龙相机,并设置相应的RAW图开关 rem ============================================================== rem 下面两个路径是手机中Raw图的存放位置,和pull出来的raw存放路径,请检查 set PHONE_JPG_PATH=sdcard/DCIM/Camera/ set PHONE_RAW_PATH=sdcard/DCIM/Camera/raw/ set PULL_RAW_PATH=./raw/ rem echo PHONE_RAW_PATH=%PHONE_RAW_PATH% rem echo PULL_RAW_PATH=%PULL_RAW_PATH% rem =============================================================== adb wait-for-device adb root adb wait-for-device adb remount rem 打开骁龙相机 rem adb shell am start -a android.media.action.STILL_IMAGE_CAMERA rem num27表示的是执行拍照动作 adb shell input keyevent 27 rem ================================================================ rem 修改颜色 color 03 echo 请稍等 ping -n 6 127.1 >nul echo 开始导出RAW文件 adb pull sdcard/DCIM/Camera/raw/ ./raw/ adb shell rm /sdcard/DCIM/Camera/raw/*.raw adb pull %PHONE_RAW_PATH% %PULL_RAW_PATH% rem ren %PULL_RAW_PATH%/*.raw %PULL_RAW_PATH%/%1_Lux.raw echo 删除phone中的RAW图成功!
但是实际使用的效果,不太好,仍然需要输入很多的命令,并且切换多个目录,一不小心就出错了,浪费比较多的时间。所以就打算进一步的改善。刚好了解到了python,学了一段时间,然后渐渐改良了原来的版本。
目前实现的效果:
代码如下:

1 # -*- coding: utf-8 -*- 2 # --------------------------------------------------------------------------- 3 # tuning capure pic tools 4 # This is a free software . 5 # 6 # Author: Qibaowei 7 # Bug report: 8 # --------------------------------------------------------------------------- 9 # 10 # Function: 11 # capure the image to local path 12 # Version: 13 # V1.0 (python 3.7) 14 # Usage: 15 # v1.pyw 16 # Example: 17 # v1.pyw 18 19 20 try: 21 import tkinter as tk 22 import tkinter.messagebox 23 from tkinter import scrolledtext 24 from tkinter import ttk 25 except: 26 print ('Following module is needed:') 27 print ('- Tkinter: check the python -v and ensure the version is py3.x') 28 sys.exit() 29 30 # cmd所依赖的系统库 31 import os 32 import time 33 import re 34 35 # 当配置栏为空时,注意弹出窗口,并且提示。 36 # 同时配置栏更改时也需要弹出提示窗口 37 38 # 配置路径 39 RawPath = "sdcard/DCIM/Camera/raw/" 40 JpgPath = "sdcard/DCIM/Camera/" 41 SavePicPath = "./raw/" 42 43 # 下拉列表中的值 44 gScenceValue=['BLC','Rollof','MCC','18%Gray','ISO12233','GrayStep','Flat Field' ] 45 gLightValue =['A','H','TL84','CWF','U30','D50','D65','D75','LED','Flash','outdoor'] 46 gLuxValue =[10,20,50,100,200,400,500,1000,'Normal','Low'] 47 gFormatValue=['raw','jpg'] 48 49 SavePicName = ' ' 50 ChosenType = 0 51 ########################################################################################### 52 class Setting: 53 def __init__(self,root): 54 global RawPath 55 global JpgPath 56 global SavePicPath 57 58 self.lf_setting = tk.LabelFrame(root, width=240, height=320,text='配置',fg='Tomato') 59 self.lf_setting.grid(columnspan=3,row=0, column=0, sticky='N'+'S'+'W'+'E',padx=10, pady=6) 60 61 62 local = tk.StringVar() 63 Raw = tk.StringVar() 64 Jpg = tk.StringVar() 65 66 local.set(SavePicPath) 67 Raw.set(RawPath) 68 Jpg.set(JpgPath) 69 70 tk.Label(self.lf_setting,text="手机RAW文件路径").grid(row=0,column=0,sticky='E') 71 tk.Label(self.lf_setting,text="手机JPG文件路径").grid(row=1,column=0,sticky='E')#第二行 72 tk.Label(self.lf_setting,text="本地保存路径").grid(row=2,column=0,sticky='E') 73 self.RawPath = tk.Entry(self.lf_setting, width=60,bg='PaleGreen',fg='red',textvariable =Raw,state = 'disabled').grid(row=0,column=1) 74 self.JpgPath = tk.Entry(self.lf_setting, width=60,bg='PaleGreen',fg='red',textvariable =Jpg,state = 'disabled').grid(row=1,column=1) 75 self.SavePath = tk.Entry(self.lf_setting, width=60,bg='PaleGreen',fg='red',textvariable =local,state = 'disabled').grid(row=2,column=1) 76 77 78 79 def GetPhoneRawPath(self): 80 print(self.RawPath.get()) 81 82 def GetPhoneJpgPath(self): 83 print(self.JpgPath.get()) 84 85 def GetSavePicPath(self): 86 print(self.SavePath.get()) 87 88 def work(self): 89 print('setting tab') 90 91 ####################################################################################### 92 class Scene: 93 def __init__(self,root): 94 self.lf_res = tk.LabelFrame(root, width=40, height=20, text='拍摄场景',fg='Maroon') 95 self.lf_res.grid(row=1, column=0, sticky='W'+'N'+'E',padx=10, pady=5) 96 # 场景 97 self.LbScene = tk.Label(self.lf_res,text="场景").grid(row=0,column=0,sticky='E',padx=10, pady=2) 98 StSceneSelect = tk.StringVar() 99 self.CChosen = ttk.Combobox(self.lf_res, width=12, textvariable=StSceneSelect, state='readonly') 100 self.CChosen['values'] = gScenceValue 101 #self.CChosen['values'] = ('BLC','Rollof','MCC','18%Gray','ISO12233','GrayStep','Flat Field') # 设置下拉列表的值 102 self.CChosen.grid(row=0,column=1) # 设置其在界面中出现的位置 column代表列 row 代表行 103 self.CChosen.current(2) # 设置下拉列表默认显示的值,0为 numberChosen['values'] 的下标值 104 self.CChosen.bind("<<ComboboxSelected>>", self.SceneSelect) 105 106 # 光源 107 self.LbLight = tk.Label(self.lf_res,text="光源").grid(row=1,column=0,sticky='E',padx=10, pady=2) 108 StLightSelect = tk.StringVar() 109 self.LightChosen = ttk.Combobox(self.lf_res, width=12,textvariable=StLightSelect, state='readonly') 110 self.LightChosen['values'] = gLightValue 111 #self.LightChosen['values'] = ('A','H','TL84','CWF','U30','D50','D65','D75','LED','Flash','outdoor') 112 self.LightChosen.grid(row=1,column=1) 113 self.LightChosen.current(2) 114 self.LightChosen.bind("<<ComboboxSelected>>", self.SceneSelect) 115 116 # 照度 117 StLux = tk.StringVar() 118 self.LbLux = tk.Label(self.lf_res,text="照度").grid(row=2,column=0,sticky='E',padx=10, pady=2) 119 self.LuxChosen = ttk.Combobox(self.lf_res, width=12, textvariable=StLux, state='readonly') 120 self.LuxChosen['values'] = gLuxValue 121 #self.LuxChosen['values'] = (10,20,50,100,200,400,500,1000,'Normal','Low') 122 self.LuxChosen.grid(row=2,column=1) 123 self.LuxChosen.current(0) 124 self.LuxChosen.bind("<<ComboboxSelected>>", self.SceneSelect) 125 126 # 格式 127 StFormat = tk.StringVar() 128 self.LbFormat = tk.Label(self.lf_res,text="格式").grid(row=3,column=0,sticky='E',padx=10, pady=2) 129 self.FormatChosen = ttk.Combobox(self.lf_res, width=12, textvariable=StFormat, state='readonly') 130 self.FormatChosen['values'] = gFormatValue 131 #self.FormatChosen['values'] = ('raw','jpg') 132 self.FormatChosen.grid(row=3,column=1) 133 self.FormatChosen.current(0) 134 self.FormatChosen.bind("<<ComboboxSelected>>", self.SceneSelect) 135 136 def SceneSelect(self,*args): 137 global SavePicName 138 global ChosenType 139 SavePicName = (self.CChosen.get()+'_'+self.LightChosen.get()+'_'+self.LuxChosen.get()+'.'+self.FormatChosen.get()) 140 print (SavePicName) 141 if self.FormatChosen.get().find("raw") != -1: 142 ChosenType = 0 143 else: 144 ChosenType = 1 145 146 def work(self): 147 print('Scene tab') 148 149 ######################################################################################### 150 class Control: 151 def __init__(self,root): 152 self.lf_control = tk.LabelFrame(root, width=40, height=20, text='控制',fg='Orange') 153 self.lf_control.grid(row=1, column=1, sticky='W'+'N',padx=10, pady=5) 154 155 self.bt_openCamera = tk.Button(self.lf_control,text='打开相机',width=10,height=2 ,command=self.OpoenCamera ) 156 self.bt_openCamera.grid(row=0,column=0,padx=10,pady=20) 157 158 self.bt_CloseCamera = tk.Button(self.lf_control,text='关闭相机',width=10,height=2,command=self.CloseCamera ) 159 self.bt_CloseCamera.grid(row=1,column=0,padx=10,pady=16) 160 161 self.bt_Capure = tk.Button(self.lf_control,text='拍照',width=10,height=2 ,command=self.CapurePicture ) 162 self.bt_Capure.grid(row=0,column=1,padx=10, pady=16) 163 164 self.bt_ClearCamera = tk.Button(self.lf_control,text='清空图片',width=10,height=2 ,command=self.ClearPicture ) 165 self.bt_ClearCamera.grid(row=1,column=1,padx=5, pady=8) 166 self.lf_message = tk.LabelFrame(root, width=100, height=50, text='实时信息',fg='DarkGreen') 167 self.lf_message.grid(columnspan=3,row=2, column=0, sticky='W'+'E'+'N'+'S',padx=10, pady=5) 168 169 self.yScrollbar = tk.Scrollbar(self.lf_message) 170 self.yScrollbar.pack(side = 'right', fill = 'y') 171 self.xScrollbar = tk.Scrollbar(self.lf_message, orient = 'horizontal')# HORIZONTAL 设置水平方向的滚动条,默认是竖直 172 self.xScrollbar.pack(side = 'bottom', fill = 'x') 173 # 创建文本框,wrap 设置不自动换行 174 self.message_out = tk.Text(self.lf_message, width = 100, 175 bg='Black',fg='DarkViolet', 176 yscrollcommand = self.yScrollbar.set, xscrollcommand = self.xScrollbar.set, wrap = 'none') 177 self.message_out.pack(anchor='center') 178 179 def OpoenCamera(self): 180 print ('open camera\n') 181 global SavePicName 182 os.popen("adb remount").read()+'\n' 183 StOpenCamera = os.popen("adb shell am start -a android.media.action.STILL_IMAGE_CAMERA").read()+'\n' 184 self.message_out.insert(tkinter.END, StOpenCamera) # INSERT表示在光标位置插入 185 self.message_out.see(tkinter.END) 186 self.message_out.update() 187 print ('rename '+(SavePicName)) 188 189 def CloseCamera(self): 190 print ('close camera\n') 191 StOpenCamera = os.popen("adb shell input keyevent 4").read()+'\n' 192 self.message_out.insert(tkinter.END, StOpenCamera) 193 self.message_out.see(tkinter.END) 194 self.message_out.update() 195 196 def CapurePicture(self): 197 print ('Capure \n') 198 global RawPath 199 global JpgPath 200 global SavePicPath 201 global SavePicName 202 global ChosenType 203 if ( ChosenType == 0): 204 StOpenCamera = os.popen("adb shell input keyevent 27").read()+'\n'+os.popen("ping -n 6 127.1 >nul").read()+'\n'+os.popen("adb pull "+RawPath+" "+SavePicPath).read()+'\n' 205 print("Choosen RAW\n") 206 else: 207 StOpenCamera = os.popen("adb shell input keyevent 27").read()+'\n'+os.popen("ping -n 6 127.1 >nul").read()+'\n'+os.popen("adb pull "+JpgPath+" "+SavePicPath).read()+'\n' 208 print("Choosen JPG\n") 209 self.message_out.insert(tkinter.END, StOpenCamera) 210 self.message_out.see(tkinter.END) 211 self.message_out.update() 212 print ("adb pull "+RawPath+" "+SavePicPath) 213 for item in os.listdir(SavePicPath): 214 if (re.match(r"^IMG_\d+", item)): 215 print (item) 216 #newname = re.sub(r"(\d+)", "", item) 217 try: 218 os.renames(SavePicPath+item, SavePicPath+SavePicName) 219 except OSError: 220 pass 221 print ("-->" + SavePicName) 222 if ( ChosenType == 0): 223 print (os.popen("adb shell rm "+RawPath+'*.raw')+'\n') 224 else: 225 print (os.popen("adb shell rm "+JpgPath+'*.jpg')+'\n') 226 227 def ClearPicture(self): 228 print ('Clear raw in phone \n') 229 g_cmd_out = os.popen("adb remount").read()+'\n' 230 self.message_out.delete('1.0','end') 231 232 def work(self): 233 print('setting tab') 234 235 ######################################################################################## 236 class Calculate: 237 def __init__(self,root): 238 self.lf_calculate = tk.LabelFrame(root, width=40, height=100, text='计算') 239 self.lf_calculate.grid(row=1, column=2, sticky='W'+'N'+'S', pady=5) 240 241 def work(self): 242 print('Calculate tab') 243 244 ####################################################################################### 245 class Message: 246 def __init__(self,root): 247 self.lf_message = tk.LabelFrame(root, width=100, height=50, text='实时信息',fg='DarkGreen') 248 self.lf_message.grid(columnspan=3,row=2, column=0, sticky='W'+'E'+'N'+'S',padx=10, pady=5) 249 250 self.yScrollbar = tk.Scrollbar(self.lf_message) 251 self.yScrollbar.pack(side = 'right', fill = 'y') 252 self.xScrollbar = tk.Scrollbar(self.lf_message, orient = 'horizontal')# HORIZONTAL 设置水平方向的滚动条,默认是竖直 253 self.xScrollbar.pack(side = 'bottom', fill = 'x') 254 # 创建文本框,wrap 设置不自动换行 255 self.message_out = tk.Text(self.lf_message, width = 100, 256 bg='Black',fg='DarkViolet', 257 yscrollcommand = self.yScrollbar.set, xscrollcommand = self.xScrollbar.set, wrap = 'none') 258 self.message_out.pack(anchor='center') 259 260 def TextUpdate(self,text): 261 print('Text update!\n') 262 self.message_out.insert(tkinter.END, text) 263 self.message_out.see(tkinter.END) 264 self.message_out.update() 265 266 def TextClear(): 267 print('Text clear') 268 self.message_out.delete('1.0','end') 269 270 def work(self): 271 print('Message tab') 272 273 ########################################################################################### 274 275 root = tk.Tk() 276 root.title('tuning拍图小助手V1.0') 277 width = 600 278 height = 480 279 screenwidth = root.winfo_screenwidth() 280 screenheight = root.winfo_screenheight() 281 size = '%dx%d+%d+%d' % (width, height, (screenwidth - width)/2, (screenheight - height)/2) 282 root.geometry(size) # width*height + pos_x + pos_y 283 284 root.columnconfigure(0, weight=1) 285 root.rowconfigure(0, weight=1) 286 287 content = tk.Frame(root) 288 content.grid(column=0, row=0, sticky=('N', 'S', 'E', 'W')) 289 app = Setting(content) 290 sence = Scene(content) 291 control = Control(content) 292 calculate = Calculate(content) 293 app.work() 294 295 content.columnconfigure(0, weight=3) 296 content.columnconfigure(1, weight=3) 297 content.columnconfigure(2, weight=3) 298 content.columnconfigure(3, weight=1) 299 content.rowconfigure(2, weight=1) 300 301 sence.work() 302 control.work() 303 calculate.work() 304 sence.SceneSelect() 305 306 root.mainloop()
后期展望:
目前只是完成了一个简单的GUI界面,并且一些基本的操作,但是逻辑部分还没有衔接上,然后找BUG优化,美化布局这些,然后打包生成exe文件。
(未完待续,后续更新!······)