432 lines
16 KiB
Python
432 lines
16 KiB
Python
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'<Residual s="([^"]+)"'
|
||
#读取文件内容
|
||
with open(rcbox_path, 'r') as f:
|
||
content = f.read()
|
||
#使用正则表达式进行匹配
|
||
match = re.search(old_value_pattern,content)
|
||
if match:
|
||
old_value = match.group(1)
|
||
new_content = re.sub(old_value_pattern,f'<Residual s="{new_value}"',content)
|
||
#重新写入进去
|
||
with open(rcbox_path, 'w') as f:
|
||
f.write(new_content)
|
||
|
||
#读取 rcbox 修改 widthHeightDepth 重建区域的深度
|
||
def change_rcbox_deepth(pid,new_value):
|
||
rcbox_path = os.path.join(config.workdir, pid, f"{pid}.rcbox")
|
||
old_value_pattern = r'<widthHeightDepth>(.*?)</widthHeightDepth>'
|
||
#读取文件内容
|
||
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'<widthHeightDepth>{strs}</widthHeightDepth>',content)
|
||
#重新写入进去
|
||
with open(rcbox_path, 'w') as f:
|
||
f.write(new_content)
|
||
|
||
def change_rcbox_center(pid,new_value):
|
||
rcbox_path = os.path.join(config.workdir, pid, f"{pid}.rcbox")
|
||
old_value_pattern = r'<center>(.*?)</center>'
|
||
#读取文件内容
|
||
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(new_value)
|
||
new_content = re.sub(old_value_pattern,f'<center>{strs}</center>',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 fileName="" />
|
||
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):
|
||
#判断pid 是否是数字
|
||
if str(pid).isdigit() == False:
|
||
return
|
||
|
||
if pid == 0 or pid == "":
|
||
return "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) |