You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

432 lines
16 KiB

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('/home/acprint/code/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)