import redis,sys,os,re,oss2,shutil,time,cv2 import json import requests import platform import numpy as np import xml.etree.ElementTree as ET from PIL import ImageGrab if platform.system() == 'Windows': #sys.path.append('e:\\libs\\') sys.path.append('libs') else: sys.path.append('/data/deploy/make3d/make2/libs/') import config,libs #判断模型是需要高精模 或者是 需要photo3 参与 def task_need_high_model_or_photo3(pid): resHigh = task_need_high_model(pid) resPhoto3 = task_need_photo3(pid) if resHigh or resPhoto3: return True else: return False #判断是否需要高精模 def task_need_high_model(pid): redis_conn = config.redis_local_common #判断在redis中是否有高精模和 需要photo3 参与的 task if redis_conn.sismember("calculateHighModel",pid): return True #判断是否需要高精模 if redis_conn.sismember("calculateHighModel_no",pid): return False #判断是否需要高精模 if libs.aliyun_face(pid): #calulate_type = 'calculateHighModel' redis_conn.sadd("calculateHighModel",pid) return True else: redis_conn.sadd("calculateHighModel_no",pid) return False #判断是否需要photo3参与建模 def task_need_photo3(pid): redis_conn = config.redis_local_common #判断在redis中是否有高精模和 需要photo3 参与的 task if redis_conn.sismember("photo3",pid): return True #判断是否需要photo3参与建模 if redis_conn.sismember("photo3_no",pid): return False if os.path.exists(os.path.join(config.workdir, pid, 'photo3')): redis_conn.sadd("photo3",pid) return True else: redis_conn.sadd("photo3_no",pid) return False #拷贝远程主机上的指定目录到本地指定目录 def copy_remote_directory(remote_host, remote_path, local_path): # 建立 SSH 连接 ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(remote_host, username='your_username', password='your_password') # 创建 SFTP 客户端 sftp = ssh.open_sftp() # 获取远程目录下的所有文件/子目录 file_list = sftp.listdir(remote_path) # 遍历远程目录中的每个文件/子目录 for file_name in file_list: remote_file_path = os.path.join(remote_path, file_name) local_file_path = os.path.join(local_path, file_name) # 判断当前项是文件还是目录 if sftp.stat(remote_file_path).st_isdir(): # 如果是目录,递归调用函数进行拷贝 os.makedirs(local_file_path, exist_ok=True) copy_remote_directory(remote_host, remote_file_path, local_file_path) else: # 如果是文件,直接拷贝到指定目录 sftp.get(remote_file_path, local_file_path) # 关闭 SFTP 客户端和 SSH 连接 sftp.close() ssh.close() #移除redis中的高精模和 需要photo3 参与的 task # def remove_redis_high_model_or_photo3(pid): # redis_conn = config.redis_local_common # redis_conn.srem("calculateHighModel",pid) # redis_conn.srem("photo3",pid) # if __name__ == '__main__': # redis_conn = config.redis_local_common # print(redis_conn.sismember("photo3_no","1")) #读取rcbox文件进行修改指定的值 def change_rcbox_s(pid,new_value): rcbox_path = os.path.join(config.workdir, pid, f"{pid}.rcbox") old_value_pattern = r'(.*?)' #读取文件内容 with open(rcbox_path, 'r') as f: content = f.read() #使用正则表达式进行匹配 match = re.search(old_value_pattern,content) if match: old_value = match.group(1) if old_value == "": return #分割字符串 arrStr = old_value.split(" ") #重新拼接字符串 strs = arrStr[0]+" "+arrStr[1]+" "+str(float(arrStr[2])+new_value) new_content = re.sub(old_value_pattern,f'{strs}',content) #重新写入进去 with open(rcbox_path, 'w') as f: f.write(new_content) #修改rcproj文件,删除没有模型的component,保留最多model 的component def changeRcprojFile(pid): # 解析XML文件 file_path = os.path.join(config.workdir, pid, f'{pid}.rcproj') #判断文件是否存在 if not os.path.exists(file_path): return False tree = ET.parse(file_path) root = tree.getroot() # 遍历所有的reconstructions节点 for reconstruction in root.findall('reconstructions'): for component in reconstruction.findall('component'): if component.find('model') == None: reconstruction.remove(component) continue # 获取所有包含model标签的component节点 components_with_model = [component for component in reconstruction.findall('component') if component.find('model') is not None] print(components_with_model) # 如果包含model标签的component节点数量大于1,则按照model数量降序排序 if len(components_with_model) > 1: components_with_model.sort(key=lambda x: len(x.findall('model')), reverse=False) for i in range(len(components_with_model)-1): reconstruction.remove(components_with_model[i]) # 保存修改后的XML文件 tree.write(file_path) return True #修改 rcproj 文件中的 controlpoints 文件的引用 def changeRcprojControlpointsFile(pid): psid = libs.getPSid(pid) # 解析XML文件 file_path = os.path.join(config.workdir, str(pid), f'{pid}.rcproj') #判断文件是否存在 if not os.path.exists(file_path): return False #下载指定的psid的 points文件到本地 flag = down_points_from_oss(pid,psid) if flag == False: return False tree = ET.parse(file_path) root = tree.getroot() # 遍历所有的reconstructions节点 for controlpoints in root.findall('controlpoints'): #修改 controlpoints 标签内容里 双引号的内容 controlpoints.set('fileName',f'controlpoints_{psid}.dat') # 保存修改后的XML文件 tree.write(file_path) return True def down_points_from_oss(pid,psid): # 根据前缀获取文件列表 prefix = f'points/{psid}/' filelist = oss2.ObjectIteratorV2(config.oss_bucket, prefix=prefix) flag = False for file in filelist: filename = file.key.split('/')[-1] if filename.endswith('.dat'): # print('正在下载:', file.key) localfile = os.path.join(config.workdir,str(pid), filename) config.oss_bucket.get_object_to_file(file.key, localfile) flag = True return flag #判断oss上是否存在指定的controlpoints文件 def isExistControlPointsOss(pid): return False psid = libs.getPSid(pid) filePath = f'points/{psid}/controlpoints_{psid}.dat' #判断oss上是否存在 if config.oss_bucket.object_exists(filePath): return True else: return False #将本地的controlpoints文件上传到oss上 def uploadControlPointsOss(pid): psid = libs.getPSid(pid) filePath = f'points/{psid}/controlpoints_{psid}.dat' localfile = os.path.join(config.workdir,str(pid), f'{str(pid)}_wait/controlpoints0.dat') #进行上传 config.oss_bucket.put_object_from_file(filePath, localfile) #截屏保存 def saveScreenImg(pid): #获取当前的日志 if not os.path.exists(os.path.join(config.workdir,"screen", time.strftime("%y%m%d",time.localtime()))): os.makedirs(os.path.join(config.workdir,"screen", time.strftime("%y%m%d",time.localtime()))) screenshot = ImageGrab .grab() screenshot.save(os.path.join(config.workdir,"screen", time.strftime("%y%m%d",time.localtime()))+"/"+str(pid)+".png") #移动到e盘 if not os.path.exists(os.path.join(config.sharedir,"screen", time.strftime("%y%m%d",time.localtime()))): os.makedirs(os.path.join(config.sharedir,"screen", time.strftime("%y%m%d",time.localtime()))) shutil.copy(os.path.join(config.workdir,"screen", time.strftime("%y%m%d",time.localtime()))+"\\"+str(pid)+".png", os.path.join(config.sharedir,"screen", time.strftime("%y%m%d",time.localtime()))) #文件夹的移动和删除 def removeFolder(pid): #判断是否存在finished文件夹,没有则创建 if not os.path.exists(os.path.join(config.workdir, 'finished')): os.makedirs(os.path.join(config.workdir, 'finished')) #移动文件夹到指定路径,如果已经存在了就删除再移动 if os.path.exists(os.path.join(config.workdir, 'finished', pid)): shutil.rmtree(os.path.join(config.workdir, 'finished', pid), ignore_errors=True) shutil.move(os.path.join(config.workdir, pid), os.path.join(config.workdir, 'finished')) #遍历finished 里的文件夹,超过三天的就都删除 for file in os.listdir(os.path.join(config.workdir, 'finished')): if os.path.isdir(os.path.join(config.workdir, 'finished', file)): file_time = os.path.getmtime(os.path.join(config.workdir, 'finished', file)) now_time = time.time() if (now_time - file_time) > 259200: shutil.rmtree(os.path.join(config.workdir, 'finished', file), ignore_errors=True) def find_last_x(image, slope_threshold = 1000): x = [] y = [] hist, bins = np.histogram(image, bins=256, range=[0, 256]) #找到50以内的最高峰 max_y = 0 max_i = 5 for i in range(5, 50): if hist[i] > max_y: max_y = hist[i] max_i = i print(f'50以内最高峰值y:{max_y},最高峰位置x:{max_i}') for i in range(2, max_i): x.append(i) y.append(hist[i]) slopes = [abs(y[i + 1] - y[i]) for i in range(len(x) - 1)] current_interval = [] max_interval = [] max_x = {} for i, slope in enumerate(slopes): current_interval.append(slope) if slope >= slope_threshold: if len(current_interval) > len(max_interval): max_interval = current_interval.copy() max_x[x[i]] = slope current_interval = [] print(max_x) last_x = list(max_x)[-1] last_y = max_x[last_x] return last_x, last_y def high_find_histogram_range(image, target_frequency): ''' 循环查找在 target_frequency (y)频次限制下的直方图区间值(x) :param image: 导入图片 :param target_frequency: 直方图 y 频次限制条件 :return: 直方图区间 x,和 该区间频次 y ''' # 计算灰度直方图 hist, bins = np.histogram(image, bins=256, range=[0, 256]) # 初始化区间和频次 interval = 255 frequency = hist[255] while frequency < target_frequency: # 更新区间和频次 interval -= 1 # 检查直方图的频次是否为None,如果频次是None,则将其设为0,这样可以避免将None和int进行比较报错。 frequency = hist[interval] if hist[interval] is not None else 0 frequency += hist[interval] if hist[interval] is not None else 0 # 如果频次接近10000则停止循环 if target_frequency - 2000 <= frequency <= target_frequency + 2000: break return interval, frequency def ps_color_scale_adjustment(image, shadow=0, highlight=255, midtones=1): ''' 模拟 PS 的色阶调整; 0 <= Shadow < Highlight <= 255 :param image: 传入的图片 :param shadow: 黑场(0-Highlight) :param highlight: 白场(Shadow-255) :param midtones: 灰场(9.99-0.01) :return: 图片 ''' if highlight > 255: highlight = 255 if shadow < 0: shadow = 0 if shadow >= highlight: shadow = highlight - 2 if midtones > 9.99: midtones = 9.99 if midtones < 0.01: midtones = 0.01 image = np.array(image, dtype=np.float16) # 计算白场 黑场离差 Diff = highlight - shadow image = image - shadow image[image < 0] = 0 image = (image / Diff) ** (1 / midtones) * 255 image[image > 255] = 255 image = np.array(image, dtype=np.uint8) return image def remove_gray_and_sharpening(jpg_path): #low_y_limit = 25000 high_y_limit = 13000 input_image = cv2.imread(jpg_path) # low_x_thresh, low_y_frequency = low_find_histogram_range(input_image, low_y_limit) low_x_thresh, low_y_frequency = find_last_x(input_image, 1000) high_x_thresh, high_y_frequency = high_find_histogram_range(input_image, high_y_limit) print(f"{low_x_thresh} 区间, {low_y_frequency} 频次") print(f"{high_x_thresh} 区间, {high_y_frequency} 频次") high_output_image = ps_color_scale_adjustment(input_image, shadow=low_x_thresh, highlight=high_x_thresh, midtones=1) cv2.imwrite(jpg_path, high_output_image, [cv2.IMWRITE_JPEG_QUALITY, 95]) # 保存图片的质量是原图的 95% #读取指定路径判断是否对齐成功 def isAlignNums(strPid): #拼接路径 csvPath = os.path.join(config.workdir, strPid, strPid+"_align.csv") print(csvPath) #判断文件是否存在 if os.path.exists(csvPath) == False: return "rebuild" #读取文件行数 lines = "" with open(csvPath, 'r') as f: lines = f.readlines() #获取长度 lines = len(lines) - 1 #获取pid对应的 photo1 和 photo2 的数量 photo1Num = 0 photo2Num = 0 for file in os.listdir(os.path.join(config.workdir, strPid, 'photo1')): if file.endswith('.jpg'): photo1Num += 1 for file in os.listdir(os.path.join(config.workdir, strPid, 'photo2')): if file.endswith('.jpg'): photo2Num += 1 #获取图片总数 totalPhotos = photo1Num + photo2Num #比对对齐数量 if totalPhotos - lines >= 4: return "rebuild" return True #消息通知 def notify(content): if content == "": return "content 不能为空" for user_agent_id in config.notify_user_Ids: data = { 'userId': user_agent_id, 'message': content, } headers = {'Content-Type': 'application/json'} message_send_url = "https://mp.api.suwa3d.com/api/qyNotify/sendMessage?userId="+user_agent_id+"&message="+content response = requests.post(message_send_url, data=json.dumps(data), headers=headers)