import os, sys, time, shlex, subprocess, shutil, requests, cv2, numpy as np from PIL import Image import platform if platform.system() == 'Windows': #sys.path.append('e:\\libs\\') sys.path.append('libs') sys.path.append('logic') else: sys.path.append('/data/deploy/make3d/make2/libs/') import config, libs, libs_db,logic_main_service def filter_dark_texture_image(pid): start_time = time.time() print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} pid: {pid} 开始检测射灯异常图片...') def get_image_v(image): # 图片左上角和右上角各取200*200区域,计算V通道均值 left_rect = image[0:200, 0:200] right_rect = image[0:200, -200:] left_hsv = cv2.cvtColor(left_rect, cv2.COLOR_BGR2HSV) left_v = left_hsv[:, :, 2] left_avg_v = np.mean(left_v) # print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} pid: {pid} 图片左上角V通道均值:{left_avg_v}') right_hsv = cv2.cvtColor(right_rect, cv2.COLOR_BGR2HSV) right_v = right_hsv[:, :, 2] right_avg_v = np.mean(right_v) # print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} pid: {pid} 图片右上角V通道均值:{right_avg_v}') return (left_avg_v + right_avg_v) / 2 v_list = [] for filename in os.listdir(os.path.join(config.workdir, pid, 'photo2')): if filename.endswith(".jpg"): image = cv2.imread(os.path.join(config.workdir, pid, 'photo2', filename)) v = get_image_v(image) item = {'filename': filename, 'v': v} v_list.append(item) v_list.sort(key=lambda x: x['v']) v_list = v_list[5: -5] avg_v = np.mean([item['v'] for item in v_list]) for item in v_list: if abs(item['v'] - avg_v) > 50: print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} pid: {pid} 图片{item["filename"]} V通道值{item["v"]},低于平均值{avg_v},将不参与贴图') libs.set_photo_join_type(config.workdir, pid, 'photo2', item['filename'].split('_')[0], mesh='1', texture='0') # 复制xmp文件到photo3目录,如果photo3目录存在的话 if os.path.exists(os.path.join(config.workdir, pid, 'photo3')): shutil.copyfile(os.path.join(config.workdir, pid, 'photo2', item['filename'].replace('jpg', 'xmp')), os.path.join(config.workdir, pid, 'photo3', item['filename'].replace('jpg', 'xmp'))) print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} pid: {pid} 射灯异常图片检测完成,共费时{libs.diff_time(start_time)}') def detect_markers(psid, pid): def fix_region(): region_filename = os.path.join(config.workdir, pid, f'{pid}.rcbox') with open(region_filename, 'r') as f: lines = f.readlines() lines = [line.replace('"NONE" globalCoordinateSystemWkt="NONE" globalCoordinateSystemName="NONE"', '"+proj=geocent +ellps=WGS84 +no_defs" globalCoordinateSystemName="local:1 - Euclidean"') for line in lines] start_time = time.time() add_photo3 = ' ' if os.path.exists(os.path.join(config.workdir, pid, 'photo3')): add_photo3 = ' -addFolder "' + os.path.join(config.workdir, pid, 'photo3') + '" ' cmd = f'{config.rcbin} {config.r2["init"]} -setInstanceName {pid} \ -save "{os.path.join(config.workdir, pid, f"{pid}.rcproj")}" \ -addFolder "{os.path.join(config.workdir, pid, "photo1")}" {config.r["setTextureFalse"]} -align -addFolder "{os.path.join(config.workdir, pid, "photo2")}" \ {add_photo3} -align -selectAllImages \ -detectMarkers "D:\\make2\\config\\detectMarkers.config.xml" \ {libs.get_defineDistances(config.ps_floor_sticker.get(psid, config.ps_floor_sticker["default"]))} -align -align -update {config.r2["setRegion"]} \ -exportXMP "D:\\make2\\config\\exportXMP.config.xml" \ -exportControlPointsMeasurements "{os.path.join(config.workdir, pid, f"{pid}.controlPoints.csv")}" "D:\\make2\\config\\exportControlPoints.config.xml" \ -exportReconstructionRegion "{os.path.join(config.workdir, pid, f"{pid}.rcbox")}" \ -save "{os.path.join(config.workdir, pid, f"{pid}.rcproj")}" -quit' print(cmd) cmd = shlex.split(cmd) res = subprocess.run(cmd) fix_region() print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} pid: {pid} 定位点检测完成, 共费时{libs.diff_time(start_time)}') if os.path.exists(os.path.join(config.workdir, pid, 'photo3')): for filename in os.listdir(os.path.join(config.workdir, pid, 'photo2')): if filename.endswith('_8.xmp'): # photo3 exist, 设置photo2的xmp文件,不参与贴图 libs.set_photo_join_type(config.workdir, pid, 'photo2', filename.split('_')[0], mesh='1', texture='0') def cal_reconstruction_region(psid, pid): def fix_region(): region_filename = os.path.join(config.workdir, pid, f'{pid}.rcbox') with open(region_filename, 'r') as f: lines = f.readlines() lines = [line.replace('"NONE" globalCoordinateSystemWkt="NONE" globalCoordinateSystemName="NONE"', '"+proj=geocent +ellps=WGS84 +no_defs" globalCoordinateSystemName="local:1 - Euclidean"') for line in lines] add_photo3 = ' ' if os.path.exists(os.path.join(config.workdir, pid, 'photo3')): add_photo3 = ' -addFolder "' + os.path.join(config.workdir, pid, 'photo3') + '" ' cmd = f'{config.rcbin} {config.r2["init"]} -setInstanceName {pid} \ -load "{os.path.join(config.workdir, pid, f"{pid}.rcproj")}" \ -addFolder "{os.path.join(config.workdir, pid, "photo2")}" -align {add_photo3} \ -detectMarkers "D:\\make2\\config\\detectMarkers.config.xml" {libs.get_defineDistances(config.ps_floor_sticker.get(psid, config.ps_floor_sticker["default"]))} -align -align \ -update {config.r2["setRegion"]} \ -exportControlPointsMeasurements "{os.path.join(config.workdir, pid, f"{pid}.controlPoints.csv")}" "D:\\make2\\config\\exportControlPoints.config.xml" \ -selectAllImages -exportXMP "D:\\make2\\config\\exportXMP.config.xml" \ -exportReconstructionRegion "{os.path.join(config.workdir, pid, f"{pid}.rcbox")}" \ -save "{os.path.join(config.workdir, pid, f"{pid}.rcproj")}" -quit' print(cmd) cmd = shlex.split(cmd) res = subprocess.run(cmd) fix_region() print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} pid: {pid} 重建区域计算完成') def step1(pid, experience=False, makeloop=True,task_distributed_id=""): libs_db.start_task({"task_type": "make", "task_key": pid}) print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} 开始处理{pid}建模任务') psid = libs.getPSid(pid) # 更新云端任务状态 res = requests.post(config.urls['update_status_modeling_url'], data={'id': pid}) print('更新建模中状态:', res.text) # 下载图片 start_time = time.time() print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} {pid} 开始下载图片...') if experience: libs.down_from_oss(config.oss_bucket, config.workdir, pid, per=50) else: libs.down_from_oss(config.oss_bucket, config.workdir, pid) os.system(f'python tools/downxmps.py {pid}') print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} {pid} 图片下载完成,共费时{libs.diff_time(start_time)}') start_time = time.time() # TODO: 上报图片采集数量,更新影棚相机状态 # 处理图片,如果experience=True,将图片缩减一半,已节省建模时间和算力成本 # if experience: # libs.resize_photos(os.path.join(config.workdir, pid, 'photo1')) # libs.resize_photos(os.path.join(config.workdir, pid, 'photo2')) # 根据配置调整photo2曝光,均衡贴图亮度,可能产生photo3 libs.adjust_photos(config.workdir, pid) # TODO: 检测模糊异常图片,上报微信通知 # TODO: 处理图片,去反光算法 # 检测图片定位点,定位异常及时上报微信通知给客服人员,人工判断是否需要重新拍摄或更换地贴。定义定位点距离,导出相机位姿信息 detect_markers(psid, pid) # 处理图片,检测photo2中的异常图片不参与贴图,以免破坏贴图效果,默认不检测射灯异常图片,以节省算力成本 if not makeloop: filter_dark_texture_image(pid) # 处理图片,暗部提亮,提高贴图效果 # TODO: 处理图片,去遮挡处理,提高建模与贴图质量 # 加入photo2,计算重建区域,导出重建区域与相机位姿配置信息 # cal_reconstruction_region(psid, pid) print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} {pid} step1图片预处理完成,共费时{libs.diff_time(start_time)}') if os.path.exists(os.path.join(config.sharedir, pid)): shutil.rmtree(os.path.join(config.sharedir, pid), ignore_errors=True) shutil.copytree(os.path.join(config.workdir, pid), os.path.join(config.sharedir, pid)) print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} {pid} step1任务完成,移动到共享目录') # TODO: 更新本地step1任务状态,加入step2任务队列 if task_distributed_id == "":#不是分布式任务的时候就自动往下个步骤走,是分布式任务的时候就就执行当前任务 if makeloop: os.system(f'python main_step2.py {pid}') else: os.system(f'python main_step2.py {pid}') # if os.path.exists(os.path.join(config.sharedir, pid)): # shutil.rmtree(os.path.join(config.sharedir, pid), ignore_errors=True) # shutil.move(os.path.join(config.workdir, pid), config.sharedir) # print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} {pid} step1任务完成,移动到共享目录') else: #分布式服务执行完后,需要更新任务状态,更新字表的finished_at字段 logic_main_service.update_task_distributed_detail({"task_distributed_id":task_distributed_id,"finished_at":time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}) return def main(pid, experience=False, makeloop=True): if pid == '0': print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} 开始进入本地任务值守模式...') while True: # 取云端redis多个key任务,TODO:后续要改为api调用 experience = False pid = libs_db.get_task('make_experience') if pid == '': time.sleep(3) pid = libs_db.get_task('make') if pid == '': time.sleep(3) continue else: experience = True step1(pid, experience, makeloop) else: step1(pid, experience, makeloop) if __name__ == '__main__': # 取云端redis任务,完成第一步的数据预处理后,将数据放入共享存储目录,将第二步任务塞入本地mysql队列 # 默认循环值守,可传参数运行单一任务,以方便调试 pid = '0' if len(sys.argv) == 2: pids = sys.argv[1].split(',') for pid in pids: main(pid, experience=False, makeloop=False) exit() if len(sys.argv) == 3: experience = False if sys.argv[2] == '1': print('演示测试...') experience = True pids = sys.argv[1].split(',') for pid in pids: main(pid, experience=experience, makeloop=False) exit() main(pid, experience=False, makeloop=True)