鱼C论坛

 找回密码
 立即注册
查看: 2003|回复: 0

[作品展示] 在电脑中搜索图片

[复制链接]
发表于 2022-9-6 10:57:13 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
C:\\WeChat Files\\wxid_48n7yufhk78y22\\FileStorage\\Video\\2022-09\\演示.mp4

  1. from tkinter import *
  2. from cv2 import imdecode,SIFT_create,FlannBasedMatcher
  3. from webbrowser import open as webopen
  4. from numpy import uint8,fromfile
  5. from threading import Thread
  6. from multiprocessing.dummy import Pool
  7. from multiprocessing import cpu_count
  8. from os import getpid, listdir, mkdir, remove, startfile, getcwd,rename,system
  9. from os.path import isdir, isfile, join, exists,getsize
  10. from tkinter.filedialog import askdirectory
  11. from windnd import hook_dropfiles
  12. from PIL import Image, ImageTk
  13. from tkinter.messagebox import showinfo, askyesno
  14. from shutil import copy
  15. # from skimage import io
  16. from pickle import dump, load
  17. from time import time
  18. # print(getpid())
  19. class APP:

  20.     def __init__(self):
  21.         self.root = Tk()
  22.         self.root.geometry('450x430')
  23.         self.root.protocol("WM_DELETE_WINDOW",func = lambda:self.dump_des(1,1))
  24.         self.root.title('星猫搜图~')
  25.         self.imgnum = 0     #用于判断文件夹内图片个数
  26.         self.if_change = 0   #判断图片数量是否变化
  27.         self.file = ''
  28.         self.des_dict = {}   #存储特征值的字典      
  29.         self.img11 = 0
  30.         self.if_dump = 0      #判断是否保存
  31.         self.progressVar = StringVar()
  32.         self.NUM_ = 0           #用于给打不开的图片重新命名
  33.         self.compare_msg  = StringVar()    #用来显示搜图进度
  34.         # self.num111 = 0
  35.         self.find_img=0       #已经搜索过的图片
  36.         self.all_pid = []       #所有子进程pid
  37.         self.me_pid = getpid()    #识别本进程pid
  38.         # print(self.me_pid)
  39.         self.badimg = 0
  40.         self.if_pool = True    #是否打开多进程
  41.         self.t = 0
  42.         '''初始话匹配图像要用的东西'''
  43.         self.sift = SIFT_create()  # 初始化 SIFT 搜索器
  44.         
  45.         self.MIN_MATCH_COUNT = 10   #最小匹配特征个数
  46.         self.nlist ={}    #图片相似点个数
  47.         FLANN_INDEX_KDTREE = 1
  48.         index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
  49.         search_params = dict(checks = 50)
  50.         self.flann = FlannBasedMatcher(index_params, search_params)  #flann算法搜索器
  51.         if not exists('星猫图片'):
  52.             mkdir('星猫图片')
  53.         self.badimglist = []
  54.         self.msg1 = StringVar(value=r'C:\Users\陈东豪\Desktop')
  55.         self.if_top123 = 0
  56.         self.filelist = []
  57.         self.if_find = 0  #表示是否找到匹配图片
  58.         # self.compare_img = 0   #用来计算进度
  59.         self.initimg = []
  60.         self.pk_file = 'des_dict.pk'     #存储特征值的文件名
  61.         if not isfile(self.pk_file):
  62.             f = open(self.pk_file, 'wb')
  63.             dump(self.des_dict, f)
  64.             f.close()
  65.         b = open(self.pk_file, 'rb')
  66.         self.des_dict = load(b)
  67.         b.close()
  68.         

  69.     def wait(self):
  70.         """等候时打开页面,显示加载过程"""
  71.         self.wait1 = Label((self.root), text='请稍等...')

  72.     def clean_file(self,*args):
  73.         '''清理掉不存在的图片地址'''
  74.         for each in self.des_dict:
  75.             if not isfile:
  76.                 del self.des_dict[each]
  77.         self.dump_des()   
  78.    
  79.     def main(self):
  80.         Label((self.root), text='img文件路径:').grid(row=0, column=0)
  81.         # self.hostVar = StringVar(value=r'C:\Users\Administrator\Desktop')
  82.         self.hostVar = StringVar()
  83.         self.numVar = StringVar(self.root, str(len(self.filelist)) + '张')
  84.         
  85.         self.filename = Entry((self.root), textvariable=(self.hostVar))
  86.         self.filename.grid(row=0, column=1)
  87.         
  88.         self.bt = Button((self.root), text='确定', command=self.change_lujing)
  89.         self.bt.grid(row=0, column=2)
  90.         self.lb4 = Label((self.root), textvariable=(self.numVar))
  91.         self.lb4.grid(row=0, column=3)
  92.         self.bt2 = Button((self.root), text='打开目标img文件夹', command=(self.open_lujing))
  93.         self.bt2.grid(row=1, column=0)
  94.         self.bt4 = Button((self.root), text='此次查找详情', command=lambda:self.show_init(self.initimg))
  95.         self.bt4.grid(row=1, column=1)
  96.         self.bt5 = Button((self.root), text='打开星猫图片', command=(self.open_files))
  97.         self.bt5.grid(row=1, column=3)
  98.         self.bt6 = Button((self.root), text='查看损坏img', command=(self.see_badimg))
  99.         self.bt6.grid(row=1, column=2)
  100.         self.balloon = Label(self.root)
  101.         self.prog = Label(self.root,textvariable=self.progressVar,font=('微软雅黑', 13, 'bold'))
  102.         self.prog.place(x=30,y=260)
  103.         self.bt.bind('<Button-3>', self.chose_dir)
  104.         self.bt4.bind('<Double-Button-3>',self.change_pool)
  105.         self.bt6.bind('<Double-Button-3>',self.clean_des)
  106.         self.bt4.bind('<Enter>', lambda event: self.Balloon_show(event,\
  107.             msg='双击鼠标右键启用多进程!\n慎重使用,很容易卡退!', fg='blue'))
  108.         self.bt6.bind('<Enter>', lambda event: self.Balloon_show(event, msg='双击鼠标右键清理缓存!', fg='blue'))
  109.         self.bt.bind('<Enter>', lambda event: self.Balloon_show(event, msg='单击鼠标右键打开文件选择框!', fg='blue'))
  110.         self.bt5.bind('<Enter>', lambda event: self.Balloon_show(event, msg='三击鼠标右键打开百度搜图\nhttps://graph.baidu.com/pcpage/index?tpl_from=pc', fg='blue'))
  111.         self.root.bind('<Leave>', self.Balloon_destroy)
  112.         self.root.bind('<Triple-Button-3>', self.baidu)
  113.         self.bt.bind('<Button-2>', lambda x:self.dump_des(1))
  114.         hook_dropfiles(self.root, func=self.dragged_files)   #当拖入文件时触发
  115.         self.display_num()
  116.         self.change_lujing()
  117.         self.clean_file()    #清理垃圾
  118.         self.root.mainloop()

  119.     def clean_des(self,*args):
  120.         '''清零des文件'''
  121.         self.des_dict = {}
  122.         file_size = getsize(self.pk_file)
  123.         showinfo('提示','之前的'+'%.2f' % (file_size/1024/1024)+'MB图片数据已清除!')
  124.         
  125.     def change_pool(self,*args):
  126.         '''是否开启多进程'''
  127.         self.if_pool = not self.if_pool
  128.         if self.if_pool:
  129.             showinfo('提示','开启多进程!\n搜图间隔不要少于三秒,不然程序缓不过来啦!')
  130.         else:
  131.             showinfo('提示','关闭多进程!')
  132.         
  133.             
  134.     def show_init(self,filename):
  135.         '''展示目标img信息'''
  136.         try:
  137.             file_msg = '\n'.join((item for item in filename))
  138.             showinfo(title='提示', message=('总共有' + str(len(filename)) + '张相似图片!\n匹配特征点个数为:' +\
  139.                 str(self.max_) + '\n' + file_msg + '\n损坏图片:' + str(self.badimg) + \
  140.                     '张\n共耗时:' + str(self.t) + 's'))
  141.         except:
  142.             showinfo('提示','暂无相关信息!')
  143.         
  144.     def Balloon_destroy(self, event):
  145.         """隐藏气泡&#702;"""
  146.         try:
  147.             self.balloon.place_forget()
  148.         except:
  149.             pass

  150.     def Balloon_show(self, event, msg, fg=None, bg=None, font=('微软雅黑', 10, 'bold')):
  151.         try:
  152.             self.balloon.place_forget()
  153.         except:
  154.             pass
  155.         else:
  156.             self.balloon['fg'] = fg
  157.             self.balloon['bg'] = bg
  158.             self.balloon['text'] = msg
  159.             self.balloon['font'] = font
  160.             self.balloon.place(x=35, y=190)

  161.     def baidu(self, event):
  162.         """百度搜图"""
  163.         webopen('https://graph.baidu.com/pcpage/index?tpl_from=pc')

  164.     def chose_dir(self, event):
  165.         '''选择文件夹'''
  166.         file = askdirectory()
  167.         if file != '':
  168.             self.hostVar.set(file)
  169.             self.change_lujing()

  170.     def open_lujing(self, *args):
  171.         """打开目标img文件夹"""
  172.         if len(self.initimg) == 0:
  173.             showinfo(title='提示', message='无目标img!')
  174.         else:
  175.             files = []
  176.             for each in self.initimg:
  177.                 files.append(each.rsplit('\\', 1)[0])
  178.             else:
  179.                 files = list(set(files))
  180.                 for each in files:
  181.                     startfile(each)

  182.     def open_files(self, *args):
  183.         """打开文件夹"""
  184.         fname = getcwd() + '//星猫图片'
  185.         startfile(fname)


  186.     def see_badimg(self, *args):
  187.         """损坏badimg"""
  188.         if len(self.badimglist) == 0:
  189.             showinfo(title='提示', message='没有badimg!')
  190.         else:
  191.             msg = '\n'.join(self.badimglist)
  192.             showinfo('badimg路径', msg)
  193.             if askyesno('警告', '是否删除损坏imgs?'):
  194.                 for each in self.badimglist:
  195.                     try:
  196.                         remove(each)
  197.                         self.badimglist.remove(each)
  198.                     except Exception as e:
  199.                         showinfo(title='提示', message=e)


  200.     def display_num(self):
  201.         """提示图片数量, 判断图片个数是否不再改变,显示搜索图片des值进度"""
  202.         if len(self.filelist)!=0 :
  203.             num_k =self.find_img/len(self.filelist)*100
  204.             if num_k<=100:
  205.                 msg = '当前文件夹下图片des值计算进度为:\n'+\
  206.                     str(num_k)+'%'
  207.                 self.progressVar.set(value=msg)    #设置进度信息
  208.         else:
  209.             msg = '当前文件夹下图片des值计算进度为:\n'+'0.00%'
  210.             self.progressVar.set(value=msg)    #设置进度信息

  211.         num = len(self.filelist)
  212.         self.numVar.set(str(num) + '张')
  213.         if self.imgnum!=num:
  214.             self.imgnum = num
  215.             self.if_change = 1
  216.         else:
  217.             if self.if_change:
  218.                 #如果变化停止则开始计算des值
  219.                 self.if_change = 0
  220.                 self.find_img=0       #已经搜索过的图片
  221.                 if len(self.filelist)>=1000:
  222.                     if not askyesno('提示','搜索文件夹超过1000张图片,是否更换小一点的文件夹?'):
  223.                         self.thread_it(self.pool_com)
  224.                     else:
  225.                         self.change_lujing()
  226.                 else:
  227.                     self.thread_it(self.pool_com)
  228.                 # showinfo('提示','开始计算图片des值啦!')
  229.         self.root.after(600, self.display_num)

  230.     def pool_com(self,*args):
  231.         '''开启多进程计算des'''
  232.         if self.if_pool:
  233.             try:
  234.                 print(cpu_count())
  235.                 pool = Pool(cpu_count()-1)  #使用核数不用拉满
  236.                 # print(pool._quick_get)
  237.                 # print(pool.getname())
  238.                 k = pool.map_async(self.compute, self.filelist) #callback可指定回调参数
  239.                 pool.close()#关闭进程池,不再接受新的进程
  240.                 pool.join()#主进程阻塞等待子进程的退出
  241.                 self.kill_pid()
  242.             except Exception as e:
  243.                 print(e)
  244.                 print('多进程出错!本次启用单进程!')
  245.                 for each in self.filelist:
  246.                     self.compute(each)
  247.         else:
  248.             for each in self.filelist:
  249.                 self.compute(each)
  250.         showinfo('提示','初始化已完成!\n可以开始使用搜图功能啦!')

  251.     # def close_pool(self,args):
  252.     #     print('已完成')

  253.     def kill_pid(self,*args):
  254.         '''杀死其余进程'''
  255.         for pid in self.all_pid:
  256.             if pid != self.me_pid:
  257.                 cmd = 'taskkill /pid ' + str(pid) + ' /f'
  258.                 print(cmd)
  259.                 try:
  260.                     system(cmd)
  261.                 except:
  262.                     pass
  263.                
  264.         self.all_pid = []   #清空进程列表
  265.         
  266.     def change_lujing(self):
  267.         """更改搜图路径"""
  268.         self.if_dump = 1
  269.         self.find_img=0
  270.         self.file = self.filename.get()
  271.         if isdir(self.file):
  272.             pass
  273.         else:
  274.             lujing = getcwd()
  275.             self.hostVar.set(lujing)
  276.             self.file = self.filename.get()
  277.         self.filelist = []
  278.         self.thread_it(self.getFileList, self.file, self.filelist)

  279.     # def pool_map(self, func, *args):
  280.     #     """多进程"""
  281.     #     pool = Pool()
  282.     #     pool.map_async(func, args)
  283.     #     pool.close()
  284.     #     pool.join()

  285.     def getFileList(self, dir, imglist, ext=['jpg', 'png', 'jpeg', 'bmp', 'webp']):
  286.         """
  287.         获取文件夹及其子文件夹中图片列表
  288.         输入 dir:文件夹根目录
  289.         输入 ext: 扩展名
  290.         返回: 文件路径列表
  291.         """
  292.         newDir = dir
  293.         if isfile(dir):
  294.             if dir.rsplit('.', 1)[(-1)] in ext:
  295.                 imglist.append(dir)

  296.         elif isdir(dir):
  297.             try:
  298.                 for s in listdir(dir):
  299.                     newDir = join(dir, s)
  300.                     self.getFileList(newDir, imglist, ext)

  301.             except Exception as e:
  302.                     print(s,'\n',e)

  303.         return imglist


  304.     #打开指定的图片,缩放到指定尺寸
  305.     def get_img(self,filename,width=445,height=365):
  306.         try:
  307.             im = Image.open(filename).resize((width,height))
  308.             # 引用:添加一个Label,用来存储图片。使用PanedWindow也行。
  309.             panel = Label(master=self.root)
  310.             panel.photo = ImageTk.PhotoImage(im)  # 将原本的变量photo改为panel.photo
  311.             return panel.photo
  312.         except:
  313.             return 0
  314.         
  315.     def compute(self,filename,if_twice=0):
  316.         '''图像特征点初始化'''
  317.         self.find_img+=1
  318.         pid=getpid()
  319.         if pid in self.all_pid:
  320.             pass
  321.         else:
  322.             self.all_pid.append(pid)
  323.         try:
  324.             if filename in self.des_dict:
  325.                 #如果字典中已存在filename特征值就不用计算
  326.                 pass
  327.             else:
  328.                 img1 = self.cv_imread(filename,1)    #加载图片
  329.                 kp1, des1 = self.sift.detectAndCompute(img1,None)   #搜索特征点
  330.                 self.des_dict[filename] = des1   #记录特征信息
  331.         except Exception as e:
  332.             if not if_twice:
  333.                 #图片名字有问题就改呗
  334.                 new_name = filename.rsplit('\\',1)[0]+'\\'+'img'+str(self.NUM_)+'.'+filename.rsplit('.',1)[-1]
  335.                 rename(filename,new_name)
  336.                 self.NUM_+=1
  337.                 # print(filename+'出错拉!'+'\n新名字:'+new_name)
  338.                 self.compute(new_name,1)  #第二次搜索
  339.             else:
  340.                 #只改一次
  341.                 print(filename+'有问题')
  342.                 pass
  343.             # print(e)


  344.                
  345.     def dragged_files(self, files):
  346.         """拖拽图片"""
  347.         self.nlist = {}   #重置
  348.         self.sum_msg2 = [item.decode('gbk') for item in files]   #要搜索的图片列表
  349.         self.NUM = 0    #搜索图片次序
  350.         self.search_img(self.sum_msg2,self.NUM)
  351.         
  352.     def search_img(self,filename = [],num=0):
  353.         '''搜索图片,可放入一个图片地址列表,num表示搜索列表第几个'''
  354.         try:
  355.             img_name0 = filename[num]
  356.             ext1 = img_name0.rsplit('.', 1)[(-1)]
  357.             try:
  358.                 self.img.destroy()
  359.             except:
  360.                 pass
  361.         
  362.             if ext1 in ('jpg', 'png', 'jpeg', 'bmp', 'webp'):
  363.                 image1 = self.get_img(img_name0)
  364.                 if image1 != 0:
  365.                     self.img = Label((self.root), image=image1)
  366.                     self.img.grid(row=2, columnspan=4)
  367.                     self.imgdict = {}   #用来保存图片相似度
  368.                     self.thread_it(self.Compare2, filename,num)
  369.                 else:
  370.                     showinfo('提示',img_name0+'\n该图片有问题!')
  371.                         
  372.             else:
  373.                 showinfo(title='提示', message=('暂不支持当前格式搜索!(' + str(ext1) + ')'))
  374.         except IndexError:
  375.             '''如果超出列表范围则停止'''
  376.             pass
  377.         
  378.     def Compare2(self, imglist=[],num=0):
  379.         '''开始搜索相似图片ing'''
  380.         img = imglist[num]
  381.         img1 = self.cv_imread(img)
  382.         
  383.         try:
  384.             kp2, self.des2 = self.sift.detectAndCompute(img1,None)   #首先计算要匹配的图片的特征值
  385.             #使用多进程加速
  386.             try:
  387.                 if self.if_pool:
  388.                     try:
  389.                         # print(1)
  390.                         pool = Pool(cpu_count()-1)
  391.                         # pool.map_async(self.compare, self.filelist,callback =lambda x :self.show_result(imglist,num))
  392.                         pool.map_async(self.compare, self.filelist,callback=lambda x:self.dispose_result())
  393.                         pool.close()#关闭进程池,不再接受新的进程
  394.                         pool.join()  #清理僵尸进程
  395.                         self.kill_pid()   #杀死进程
  396.                     except Exception as e:
  397.                         print(e)
  398.                         print('多进程出错!请重试')
  399.                         # for each in self.filelist:
  400.                         #     self.compare(each)
  401.                 else:
  402.                     for each in self.filelist:
  403.                         self.compare(each)
  404.                     self.dispose_result()
  405.             except:
  406.                 showinfo('提示','请等待当前进程处理完毕!')
  407.         except:
  408.             showinfo('提示', '此图片损坏,请更换其他图片!')


  409.     def dispose_result(self,*args):
  410.         '''处理最后的结果'''   
  411.         self.show_result(self.sum_msg2,self.NUM) #显示查找结果
  412.         # print(self.nlist)
  413.         self.NUM+=1    #次序加1
  414.         if self.NUM <= (len(self.sum_msg2)-1):
  415.             self.search_img(self.sum_msg2,self.NUM)  #开始搜索下张图片
  416.             
  417.   
  418.             
  419.     def show_result(self,imglist=[],num =0 ):
  420.         '''用来展示搜索结果'''
  421.         try:
  422.             t1 = time()
  423.             self.max_ = max(self.nlist.values())
  424.             print(self.max_)
  425.             # print(self.min_)
  426.             max_key = [i for i in self.nlist.keys() if self.nlist[i] == self.max_]
  427.             # print(max_key)
  428.             self.initimg = max_key
  429.             for each_img in max_key:
  430.                 try:
  431.                     copy(each_img, './/星猫图片')
  432.                 except Exception as e:
  433.                     print(e)
  434.                 if not isfile(each_img):
  435.                     del self.des_dict[each_img]
  436.                     max_key.remove(each_img)
  437.             if max_key!=[]:
  438.                 self.show_img1(max_key)
  439.             self.dump_des()   #数据保存
  440.             t2 = time()
  441.             self.t = t2 - t1

  442.         except Exception as e:
  443.             showinfo('提示',e)

  444.                
  445.     def dump_des(self,if_display=0,if_destroy=0):
  446.         '''存des'''
  447.         # print(1)
  448.         f=  open(self.pk_file, 'wb')
  449.         dump(self.des_dict, f)   #存储图片的特征值
  450.         f.close()
  451.         if if_display:
  452.             showinfo('提示','图片des数据已保存!')
  453.         if if_destroy:
  454.             self.root.destroy()
  455.             
  456.             
  457.             
  458.     def show_msg(self, title1='提示', msg='呱呱呱!', ms=0, fontbig=20):
  459.         """显示消息"""
  460.         if msg != self.msg1:
  461.             self.msg1.set(msg)
  462.         if not self.if_top123:
  463.             self.top123 = Toplevel()
  464.             self.top123.title(title1)
  465.             self.top123.protocol('WM_DELETE_WINDOW', self.destroytop)
  466.             msg1 = Label((self.top123), textvariable=(self.msg1), font=('微软雅黑', fontbig, 'bold'))
  467.             msg1.pack()
  468.         if ms != 0:
  469.             self.top123.after(ms, self.destroytop)

  470.     def destroytop(self):
  471.         """关闭窗口"""
  472.         self.top123.destroy()
  473.         self.if_top123 = 0

  474.     def show_img1(self, filename=[]):
  475.         """传入图像地址列表,可显示多张照片"""
  476.         print(filename)
  477.         for each_name in  filename:
  478.             top11 = Toplevel()
  479.             top11.title(each_name)
  480.             top11.geometry('640x400')
  481.             img2 = Label((top11), image=(self.get_img(each_name, 640, 400)))
  482.             img2.pack()

  483.     def thread_it(self, func, *args):
  484.         """多线程"""
  485.         t = Thread(target=func, args=args)
  486.         t.setDaemon(True)
  487.         t.start()



  488.     def cv_imread(self, filePath,num=1):
  489.         '''使得imread可以读取中文路径,读取灰度值照片
  490.         num=0表示读取灰度照片,num=-1表示读取完整照片,num=1表示读取彩图'''
  491.         return imdecode(fromfile(filePath, dtype=(uint8)),num)



  492.     def compare(self,filename,*args):
  493.         '''图片名字,是否第二次搜索'''
  494.         pid = getpid()
  495.         if pid in self.all_pid:
  496.             pass
  497.         else:
  498.             self.all_pid.append(pid)
  499.         try:
  500.             #计算匹配点
  501.             matches = self.flann.knnMatch(self.des_dict[filename],self.des2,k=2)
  502.             # store all the good matches as per Lowe's ratio test.
  503.             num = 0 #计算点个数
  504.             for m,n in matches:
  505.                 if m.distance < 0.7*n.distance:
  506.                     #符合要求的点才算数
  507.                     num+=1
  508.             if num>=self.MIN_MATCH_COUNT:
  509.                 self.nlist[filename] = num    #记录特征点个数
  510.         except Exception as e:
  511.             print(e)
  512.         

  513. if __name__ == '__main__':
  514.         a = APP()
  515.         a.main()

复制代码
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-4-27 03:41

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表