Advertisement
Guest User

Untitled

a guest
Feb 22nd, 2021
104
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 50.47 KB | None | 0 0
  1. from vidstab import VidStab, vidstab_utils
  2. import math
  3. import random
  4. from cv2 import cv2
  5. import imutils.feature.factories as kp_factory
  6. import numpy as np
  7. import argparse
  8. from PIL import Image
  9. import subprocess
  10. from matplotlib import pyplot as plt
  11. from typing import List, Set, Union
  12. import imutils
  13. import json
  14.  
  15. kp_method = 'DENSE'
  16. # stabilizer = VidStab(kp_method=kp_method)
  17. # TODO: 1. Фильтр Калмана
  18. # 2. Декомпозиция с последующим удалением эффекта rolling-shutter
  19. # 3. Определение фантомных кадров
  20. # 4. Импейтинг. Идея для импейтинга - использовать PTTools
  21. # 5. Удаление фона
  22. # 6. Нужно придумать, что делать с фантомными кадрами
  23.  
  24. width = 1920
  25. height = 1080
  26.  
  27. # stabilizer.gen_transforms('sample2.mp4')
  28. # kp_detector = kp_factory.FeatureDetector_create('DENSE', step=22, radius=0.5)
  29. # kp_detector = kp_factory.FeatureDetector_create('DENSE', step=15, radius=0.5)
  30. # kp_detector = kp_factory.FeatureDetector_create('BRISK', thresh=10)
  31. # kp_detector = kp_factory.FeatureDetector_create('FAST')
  32.  
  33. def probabilistic_round(x):
  34. return int(math.floor(x + random.random()))
  35.  
  36. def split_into_blocks(array, nrows, ncols):
  37. """Разбивает 2D-изображение на блоки (nrows, ncols).
  38.  
  39. См. https://stackoverflow.com/a/51914911, https://stackoverflow.com/a/16858283
  40. """
  41.  
  42. h, w = array.shape
  43. assert h % nrows == 0
  44. assert w % ncols == 0
  45. return (array.reshape(h // nrows, nrows, -1, ncols)
  46. .swapaxes(1, 2)
  47. .reshape(-1, nrows, ncols))
  48.  
  49. def is_duplicate_frame_ffmpeg(prev_frame_gray, cur_frame_gray, thresh_lo = 64 * 5, thresh_hi = 64 * 12, frac = 0.33):
  50. """Алгоритм поиска дупликатов кадров из ffmpeg mpdecimate.
  51.  
  52. Считаем отличия по блокам 8x8.
  53. Считаем число блоков, отличающихся на hi, число блоков, отличающихся lo.
  54. Если хоть один блок отличается на hi, или доля блоков, отличающихся на lo больше frac, то это не дупликат.
  55. """
  56.  
  57. frame_diff = cv2.absdiff(prev_frame_gray, cur_frame_gray)
  58. blks = split_into_blocks(frame_diff, 8, 8)
  59. sum_diff = np.sum(blks, (1, 2))
  60. num_lo_blks = np.count_nonzero(sum_diff > thresh_lo)
  61. num_hi_blks = np.count_nonzero(sum_diff > thresh_hi)
  62. num_total_blks = len(sum_diff)
  63. return num_hi_blks == 0 and (num_lo_blks / num_total_blks) < frac
  64.  
  65. def find_all_duplicates(input_path: str):
  66. vid = cv2.VideoCapture(input_path)
  67. frame_gray = prev_frame_gray = None
  68. dups = []
  69. i = 1
  70. while True:
  71. retval, frame = vid.read()
  72. if not retval:
  73. break
  74. prev_frame_gray = frame_gray
  75. frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
  76. if is_duplicate_frame_ffmpeg(prev_frame_gray, frame_gray):
  77. yield i, frame
  78. vid.release()
  79.  
  80. # def gen_mid_frames(input_path: str):
  81. # for i, frame in find_all_duplicates(input_path):
  82.  
  83. def write_keypoints_to_ffmpeg_vidstab(prev_kps, cur_kps, scale=1.0):
  84. allkps = []
  85. for kp1, kp2 in zip(prev_kps, cur_kps):
  86. kp1x = kp1[0]
  87. kp1y = kp1[1]
  88. kp2y = kp2[1]
  89. kp2x = kp2[0]
  90. # if kp1x > 0.25 * width and kp1x < 0.75 * width and kp1y > 0.25 * width and kp1y < 0.75 * width:
  91. # continue
  92. kp_str = '(LM {0} {1} {2} {3} 112 0.5 0.5)'.format(
  93. probabilistic_round((kp2x - kp1x) * scale),
  94. probabilistic_round((kp2y - kp1y) * scale),
  95. probabilistic_round(kp2x),
  96. probabilistic_round(kp2y))
  97. allkps.append(kp_str)
  98.  
  99. return '(List {0} [{1}])'.format(len(allkps), ','.join(allkps))
  100.  
  101. # prev_kps_raw = None
  102. prev_kps_high_contrast = None
  103. write_ffmpeg_vidstab = True
  104.  
  105. trf_file = None
  106.  
  107. # class VideoWrapper:
  108. # def __init__(self, video):
  109. # self._video = video
  110. # self._framebuf = []
  111. # self._curFrameIdx = 0
  112. # self._frames = 0
  113.  
  114. # def getFrame(self, idx):
  115. # cvvideo.next()
  116.  
  117. # def nextFrame(self):
  118. # self._curFrameIdx += 1
  119.  
  120. class FrameInfo:
  121. def __init__(self, transform, is_bad: bool, is_dup: bool):
  122. """
  123. @is_bad Плохой кадр, его не должно быть в выходном потоке
  124. """
  125. self.transform = transform
  126. self.is_bad_frame = is_bad
  127. self.is_duplicate = is_dup
  128.  
  129. def detect_kps(frame_gray, dense_step=30):
  130. kp_detector = kp_factory.FeatureDetector_create('DENSE', step=dense_step, radius=0.5)
  131. # kp_detector = kp_factory.FeatureDetector_create('BRISK', thresh=10)
  132. kps_raw = kp_detector.detect(frame_gray)
  133.  
  134. # TODO: ЧТО ЭТО ТАКОЕ???? Видимо, дополнительная фильтрация от нельда, темных предметов.
  135. # Но это довольно скверная идея, плюс я ведь использую детектор фигуристов. А на показалках лед может быть темным.
  136. # Я ведь отказывался от этого? Идея же не рабочая.
  137. # kps_raw_filtered = []
  138. # for kp in kps_raw:
  139. # if frame_gray[min(int(kp.pt[1]), 1079), min(int(kp.pt[0]), 1919)] + 10 > random.randint(0, 255):
  140. # kps_raw_filtered.append(kp)
  141. # kps_raw = kps_raw_filtered
  142.  
  143. kps = np.array([kp.pt for kp in kps_raw], dtype='float32').reshape(-1, 1, 2)
  144. return kps_raw, kps
  145.  
  146. # TODO: этот код - это такая дичь. Нужно все это рефакторить.
  147. class TransInfo:
  148. def count_inliers(self):
  149. # TODO: почему не работает count_nonzero()?
  150. i = 0
  151. for kpp0, kpp1, is_inlier in zip(self.prev_inlier_keypoints, self.cur_inlier_keypoints, self.inliers):
  152. if is_inlier:
  153. i += 1
  154. return i
  155.  
  156.  
  157. def __init__(self, frame_gray, prev_frame_gray, skaters, dense_step=30, noinl=False, win_size=30):
  158. self.kps_raw, self.kps = detect_kps(prev_frame_gray, dense_step=dense_step)
  159. self.kps = filter_keypoints(self.kps, skaters)
  160.  
  161. if len(self.kps) > 0:
  162. self.optical_flow = cv2.calcOpticalFlowPyrLK(prev_frame_gray, frame_gray, self.kps, None, winSize=(win_size, win_size), maxLevel=5)
  163. self.prev_matched_keypoints, self.cur_matched_keypoints = vidstab_utils.match_keypoints(self.optical_flow, self.kps)
  164.  
  165. if len(self.kps) == 0 or len(self.prev_matched_keypoints) < 5:
  166. self.transform = [[1, 0, 0], [0, 1, 0]]
  167. self.inliers = np.array([])
  168.  
  169. self.cur_inlier_keypoints = np.array([])
  170. self.prev_inlier_keypoints = np.array([])
  171. self.bbox = [0, 0, 0, 0]
  172.  
  173. else:
  174.  
  175. self.transform, self.inliers = cv2.estimateAffine2D(
  176. np.array(self.prev_matched_keypoints), np.array(self.cur_matched_keypoints))
  177.  
  178. self.cur_inlier_keypoints = np.array(self.cur_matched_keypoints)[np.array(self.inliers) == 1]
  179. self.prev_inlier_keypoints = np.array(self.prev_matched_keypoints)[np.array(self.inliers) == 1]
  180. self.bbox = cv2.boundingRect(np.float32(self.cur_inlier_keypoints))
  181.  
  182.  
  183. another_trans = None
  184. inlier_cnt = self.count_inliers()
  185. # self.scheme = f'[DENSE {1}]'
  186. # print(inlier_cnt, noinl)
  187. if inlier_cnt < 350 and not noinl:
  188. eq_hist_trans = TransInfo(cv2.equalizeHist(frame_gray), cv2.equalizeHist(prev_frame_gray), skaters=skaters,
  189. dense_step=dense_step, noinl=True, win_size=40)
  190. if eq_hist_trans.count_inliers() * 1.2 > inlier_cnt:
  191. print (f"yes, eq hist is better {eq_hist_trans.count_inliers()} > {inlier_cnt}")
  192. another_trans = eq_hist_trans
  193. inlier_cnt = another_trans.count_inliers()
  194.  
  195. if inlier_cnt < 350 and dense_step > 15:
  196. dense_trans = TransInfo(frame_gray, prev_frame_gray, skaters=skaters, dense_step=15, noinl=True, win_size=40)
  197. if dense_trans.count_inliers() * 1.2 > inlier_cnt:
  198. print (f"yes, high density is better {dense_trans.count_inliers()} > {inlier_cnt}")
  199. another_trans = dense_trans
  200. inlier_cnt = another_trans.count_inliers()
  201.  
  202. if inlier_cnt < 350 and dense_step > 10:
  203. dense_trans = TransInfo(frame_gray, prev_frame_gray, skaters=skaters, dense_step=10, noinl=True, win_size=40)
  204. if dense_trans.count_inliers() * 1.2 > inlier_cnt:
  205. print (f"yes, very high density is better {dense_trans.count_inliers()} > {inlier_cnt}")
  206. another_trans = dense_trans
  207.  
  208. if another_trans:
  209. self.inliers = another_trans.inliers
  210. self.kps = another_trans.kps
  211. self.kps_raw = another_trans.kps_raw
  212. self.optical_flow = another_trans.optical_flow
  213. self.cur_inlier_keypoints = another_trans.cur_inlier_keypoints
  214. self.prev_inlier_keypoints = another_trans.prev_inlier_keypoints
  215. self.prev_matched_keypoints = another_trans.prev_matched_keypoints
  216. self.transform = another_trans.transform
  217. self.bbox = another_trans.bbox
  218.  
  219.  
  220. class TrfWriter:
  221. def __init__(self):
  222. pass
  223.  
  224. def detect_skaters(image):
  225. image = imutils.resize(image, width=960)
  226. hog = cv2.HOGDescriptor()
  227. hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
  228. PADDING = 80
  229. CENTRAL_RECT = (300, 300)
  230. EXCLUDE_CENTRAL_RECT = True
  231.  
  232. (rects, weights) = hog.detectMultiScale(image, winStride=(4, 4), padding=(PADDING, PADDING), scale=1.15)
  233.  
  234. if EXCLUDE_CENTRAL_RECT:
  235. central_rect = np.array([(960 / 2 - CENTRAL_RECT[0] / 2, 540 / 2 - CENTRAL_RECT[1] / 2, CENTRAL_RECT[0], CENTRAL_RECT[1])], dtype=int)
  236. if len(rects) == 0:
  237. rects = central_rect
  238. else:
  239. rects = np.concatenate((rects, central_rect))
  240. # if len(rects):
  241. # rects +=
  242.  
  243. # print (rects, weights)
  244. return rects * 2, weights
  245.  
  246. def filter_keypoints(kps, skaters):
  247. filtered_kps = []
  248. for kp in list(kps):
  249. hitsbbox = False
  250. for sk in skaters:
  251. if (kp[0, 0] > sk[0] and kp[0, 1] > sk[1] and
  252. kp[0, 0] < sk[0] + sk[2] and kp[0, 1] < sk[1] + sk[3]):
  253. hitsbbox = True
  254. break
  255. if not hitsbbox:
  256. filtered_kps.append(kp)
  257. return np.array(filtered_kps)
  258.  
  259. def gen_motion_vectors(input_path: str, write_ffmpeg_vidstab: bool, trf_path: str, duplicate_set: Set[int]):
  260. vid = cv2.VideoCapture(input_path)
  261.  
  262. prev_frame_gray = None
  263. frame_idx = 0
  264. prev_kps = None
  265. camera_trajectory = []
  266. num_dropped = 0
  267.  
  268. input_width = int(vid.get(cv2.CAP_PROP_FRAME_WIDTH))
  269. input_height = int(vid.get(cv2.CAP_PROP_FRAME_HEIGHT))
  270. input_fps = vid.get(cv2.CAP_PROP_FPS)
  271. framecount = vid.get(cv2.CAP_PROP_FRAME_COUNT)
  272.  
  273. if write_ffmpeg_vidstab:
  274. # trf_path = input_path + '.trf'
  275. trf_file = open(trf_path, 'w')
  276. trf_file.writelines(['VID.STAB 1\n# generated by python vidstab, keypoints = {0}\nFrame 1 (List 0 [])\n'.format(kp_method)])
  277.  
  278. # 59.94
  279. if False:
  280. debug_vid = FfmpegVideoWriter(input_path + '.debug.mp4', fps=input_fps, quality=20, filters='',
  281. w=input_width, h=input_height, duration_s=framecount / input_fps, audio_file=None)
  282. else:
  283. debug_vid = None
  284. old_skaters = []
  285. prev_was_duplicate = False
  286. duplicate_count = 0
  287.  
  288. while True:
  289. is_duplicate = duplicate_set and frame_idx != 0 and frame_idx + 1 in duplicate_set
  290. retval, frame = vid.read()
  291. if not retval:
  292. break
  293. if not is_duplicate:
  294. frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
  295.  
  296. # frame_gray = cv2.convertScaleAbs(frame_gray, alpha=5, beta=-1000)
  297. # frame_gray = cv2.adaptiveThreshold(frame_gray,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY,11,2)
  298. # frame_gray = cv2.equalizeHist(frame_gray)
  299. # frame_gray = cv2.normalize(frame_gray, None, 0, 255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
  300. # frame_gray = np.min(np.max((frame_gray.astype(np.float32) - 127) * 6, 255.0), 0.0).astype(np.uint8)
  301.  
  302. # frame_gray = (frame_gray - 150) * 10
  303. # frame_gray = (frame_gray - 150) * 10
  304.  
  305. if frame_idx == 0:
  306. # prev_kps_raw, prev_kps = detect_kps(frame_gray)
  307. pass
  308.  
  309. else:
  310. # Дубликаты требуют специального обхождения
  311. is_duplicate = is_duplicate or is_duplicate_frame_ffmpeg(prev_frame_gray, frame_gray)
  312. if is_duplicate:
  313. duplicate_count += 1
  314. # is_dropped = True if frame_idx == 80 else False
  315. # is_duplicate = False
  316. is_dropped = False
  317.  
  318. if is_duplicate:
  319. print ('DUPLICATE')
  320. if write_ffmpeg_vidstab:
  321. trf_file.writelines(['Frame {0} (List 0 [])\n'.format(num_dropped + frame_idx + 1)])
  322. camera_trajectory.append(FrameInfo([[1, 0, 0], [0, 1, 0]], is_bad=is_dropped, is_dup=is_duplicate))
  323.  
  324. if not is_duplicate:
  325. # Если не можем найти фигуристов, используем с предыдущего кадра
  326. skaters, _ = detect_skaters(frame_gray)
  327. if len(skaters) == 0 and len(old_skaters) != 0:
  328. skaters = old_skaters
  329. else:
  330. old_skaters = skaters
  331.  
  332. ti_lowcontrast = TransInfo(frame_gray, prev_frame_gray, skaters)
  333. ti = ti_lowcontrast
  334.  
  335. if write_ffmpeg_vidstab:
  336. # TODO: переделать
  337. prev_inlier_keypoints1 = []
  338. cur_inlier_keypoints1 = []
  339. for kpp0, kpp1, is_inlier in zip(ti.prev_inlier_keypoints, ti.cur_inlier_keypoints, ti.inliers):
  340. if is_inlier:
  341. prev_inlier_keypoints1.append(kpp0)
  342. cur_inlier_keypoints1.append(kpp1)
  343. scale = 1.0 if (not is_dropped and not prev_was_duplicate) else 0.5
  344. points_str = write_keypoints_to_ffmpeg_vidstab(prev_inlier_keypoints1, cur_inlier_keypoints1, scale)
  345.  
  346. # Если обнаружили фантомный дропнутый кадр, то вставляем два фрейма с половиной движения
  347. trf_file.writelines(['Frame {0} {1}\n'.format(num_dropped + frame_idx + 1, points_str)])
  348. if is_dropped:
  349. trf_file.writelines(['Frame {0} {1}\n'.format(num_dropped + frame_idx + 2, points_str)])
  350.  
  351. if is_dropped:
  352. camera_trajectory.append(FrameInfo([[1, 0, 0], [0, 1, 0]], is_bad=is_dropped, is_dup=is_duplicate))
  353. camera_trajectory.append(FrameInfo(ti.transform, is_bad=False, is_dup=False))
  354.  
  355. print (f'frame={frame_idx} inliers={ti.count_inliers()}')
  356. # if ti.count_inliers() < 400: # 100
  357. # matched_raw_kps = [kpp for kpp, is_matched in zip(kps_raw, optical_flow[1]) if is_matched]
  358. # kpps_inliers = [kpp for kpp, is_inlier in zip(matched_raw_kps, inliers) if is_inlier]
  359. if debug_vid:
  360. outImg = np.zeros(frame_gray.shape, np.uint8,order='C').ravel()
  361. outImg = cv2.drawKeypoints(frame_gray, ti.kps_raw, outImg)
  362. for sk in skaters:
  363. cv2.rectangle(outImg, (sk[0], sk[1]), (sk[2] + sk[0], sk[3] + sk[1]), (255, 255, 255), thickness=5)
  364.  
  365. cv2.rectangle(outImg, (ti.bbox[0], ti.bbox[1]), (ti.bbox[0] + ti.bbox[2], ti.bbox[1] + ti.bbox[3]), (0, 255, 0), 3)
  366. for kpp0, kpp1, is_inlier in zip(ti.prev_inlier_keypoints, ti.cur_inlier_keypoints, ti.inliers):
  367. color = (0, 255, 0) if is_inlier else (0, 0, 255)
  368. cv2.line(outImg, (kpp0[0], kpp0[1]), (kpp1[0], kpp1[1]), (0,0,255), 1)
  369. cv2.putText(outImg, f'FRAME {frame_idx}', (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
  370. debug_vid.write_frame(outImg)
  371. # cv2.imwrite('out1/frame{0}.png'.format(frame_idx), outImg)
  372. # cv2.imwrite('out1/frame_clean{0}.png'.format(frame_idx), frame_gray)
  373.  
  374. # prev_kps = ti.kps
  375. # prev_kps_raw = kps_raw
  376.  
  377. if is_dropped:
  378. num_dropped += 1
  379.  
  380. # print(frame_idx)
  381. prev_was_duplicate = is_duplicate
  382. # print()
  383.  
  384. frame_idx += 1
  385. prev_frame_gray = frame_gray
  386.  
  387. vid.release()
  388.  
  389. if debug_vid:
  390. debug_vid.release()
  391.  
  392. stats = {
  393. 'duplicate_count': duplicate_count
  394. }
  395.  
  396. return camera_trajectory, stats
  397.  
  398. def trajectory_smoother(frames):
  399. return frames
  400.  
  401. ffmpeg_executable = 'ffmpeg'
  402.  
  403. def fps_to_ffmpeg_fps(fps: float):
  404. if round(fps * 100) == 5994:
  405. return '60000/1001'
  406. elif round(fps * 100) % 100 == 0:
  407. return str(int(fps))
  408. return str(fps)
  409.  
  410. def dnxhd_bitrate(w: int, h: int, fps: float):
  411. if w == 1920 and h == 1080:
  412. if int(round(fps)) == 60:
  413. return 'yuv422p', '440M', None
  414. elif int(round(fps)) == 50:
  415. return 'yuv422p', '365M', None
  416. elif int(round(fps)) == 30:
  417. return 'yuv422p', '220M', None
  418. elif w == 3840 and h == 2160:
  419. if int(round(fps)) == 60:
  420. return 'yuv422p10le', '1760M', 'dnxhr_hqx'
  421. elif int(round(fps)) == 50:
  422. return 'yuv422p10le', '1460M', 'dnxhr_hqx'
  423. elif int(round(fps)) == 30:
  424. return 'yuv422p10le', '880M', 'dnxhr_hqx'
  425. raise Exception(f'fps {fps} incompatible with dnxhd')
  426.  
  427. class FfmpegVideoWriter:
  428. def __init__(self, path: str, quality: Union[int, str], w: int, h: int, duration_s: float, fps: float, filters: str = None, preset: str = 'slow', audio_file: str = None):
  429. assert isinstance(quality, str) or quality >= 0 and quality < 51
  430. fps_str = format(fps, '.2f')
  431. args = [ffmpeg_executable, '-y', '-f', 'image2pipe', '-vcodec', 'bmp', '-t', '00:{0}:{1}'.format(int(duration_s // 60), duration_s % 60), '-video_size', '{0}x{1}'.format(int(w), int(h)) , '-r', fps_str, '-i', '-']
  432. if audio_file:
  433. # args += ['-i', audio_file]
  434. args += ['-i', audio_file, '-map', '0:v:0', '-map', '1:a:0']
  435.  
  436. if filters:
  437. args += ['-vf', filters]
  438.  
  439. res_str = f'{w}x{h}'
  440. if quality == 'dnxhd':
  441. pix_fmt, bitrate, profile = dnxhd_bitrate(w, h, fps)
  442. args += ['-vcodec', 'dnxhd', '-acodec', 'pcm_s16le', '-s', res_str, '-b:v', bitrate, '-s', res_str, '-r', fps_to_ffmpeg_fps(fps), '-pix_fmt', pix_fmt]
  443. if profile:
  444. args += ['-profile:v', profile]
  445. args += ['-f', 'mov', path]
  446. else:
  447. # args += ['-vcodec', 'dnxhd', '-acodec', 'pcm_s16le', '-s', '1920x1080', '-b:v', '365M', '-s', '1920x1080', '-r', '50', '-pix_fmt', 'yuv422p', '-f', 'mov', path]
  448. args += ['-vcodec', 'libx264', '-crf', str(quality), '-preset', preset, '-r', fps_str, path]
  449. print(args)
  450. self.p = subprocess.Popen(args, stdin=subprocess.PIPE)
  451.  
  452. def write_frame(self, cvframe):
  453. rgbimg = cv2.cvtColor(np.array(cvframe, dtype=np.uint8), cv2.COLOR_BGR2RGB)
  454. img = Image.fromarray(rgbimg)
  455. img.save(self.p.stdin, 'BMP')
  456.  
  457. def release(self):
  458. self.p.stdin.close()
  459. self.p.wait()
  460.  
  461. # import bright
  462.  
  463. # def remove_black_frames(input_path, output_path, frames_list, audio_input):
  464. # i = 0
  465.  
  466.  
  467. def render(input_path: str, output_path: str, trf_path: str, trajectory, quality, smoothness=20, bright_correct=False):
  468. vid = cv2.VideoCapture(input_path)
  469.  
  470. if bright_correct:
  471. vid = bright.create_brightness_from_video_capture(vid)
  472.  
  473. baddies = []
  474. dups = []
  475. for frame_idx, frame_info in enumerate(trajectory):
  476. if frame_info.is_bad_frame:
  477. baddies.append(frame_idx)
  478. if frame_info.is_duplicate:
  479. dups.append(frame_idx)
  480.  
  481. # :maxangle=0
  482. vidstab_filter = 'vidstabtransform=input={0}:zoom=0:smoothing={1}:interpol=bicubic'.format(trf_path, smoothness) # :crop=black
  483. unsharp_filter = 'unsharp=5:5:0.4:3:3:0.2'
  484. additional_filters = 'eq=brightness=0.05:contrast=1.1:gamma=1.05:saturation=1.1'
  485.  
  486. # filters = [vidstab_filter, unsharp_filter, additional_filters]
  487. filters = [vidstab_filter, unsharp_filter]
  488. # filters = [unsharp_filter]
  489.  
  490. # # почему-то вариант с селектом не работает, используем промежуточный файл
  491. # if len(baddies) > 0:
  492. # tmp_path = output_path + '.tmp.mov'
  493. # output_vid_path = tmp_path
  494. # else:
  495. # output_vid_path = output_path
  496.  
  497. baddies_s = []
  498. for i, bad in enumerate(baddies):
  499. baddies_s.append(i + bad)
  500.  
  501. if len(baddies) > 0 or len(dups):
  502. good_selector = '*'.join(['not(eq(n\\,{0}))'.format(bad_idx) for bad_idx in sorted(baddies + dups)])
  503. select_only_good_filter = 'select={0}'.format(good_selector)
  504. filters.append(select_only_good_filter)
  505.  
  506. if len(baddies) > 0:
  507. setpts_filter = 'setpts=N/FRAME_RATE/TB'
  508. # setpts_filter = 'setpts=\'PTS-(1/FR/TB)*({0})\''.format('+'.join(['gte(N\\,{0})'.format(bad_idx) for bad_idx in baddies_s]))
  509. filters.append(setpts_filter)
  510.  
  511. filters_str = ','.join(filters)
  512.  
  513. input_width = int(vid.get(cv2.CAP_PROP_FRAME_WIDTH))
  514. input_height = int(vid.get(cv2.CAP_PROP_FRAME_HEIGHT))
  515. input_fps = vid.get(cv2.CAP_PROP_FPS)
  516. framecount = vid.get(cv2.CAP_PROP_FRAME_COUNT)
  517.  
  518. print(input_fps)
  519. print(filters_str)
  520.  
  521. # 59.94
  522. outputvid = FfmpegVideoWriter(output_path, fps=input_fps, quality=quality, filters=filters_str, w=input_width, h=input_height,
  523. duration_s=(framecount + len(baddies)) / input_fps, audio_file=input_path)
  524.  
  525. for frame_idx, frame_info in enumerate(trajectory):
  526. if frame_info.is_bad_frame:
  527. black_frame = np.zeros((input_height, input_width, 3), np.uint8)
  528. outputvid.write_frame(black_frame)
  529.  
  530. # TODO: удалить, это какая-то ошибка, дупликаты не должны влиять на рендеринг
  531. elif frame_info.is_duplicate:
  532.  
  533. retval, frame = vid.read()
  534. if not retval:
  535. print ('Ошибка при чтение входного потока')
  536. break
  537.  
  538. print ('DUPLICATE')
  539. pass
  540.  
  541. else:
  542. retval, frame = vid.read()
  543. if not retval:
  544. print ('Ошибка при чтение входного потока')
  545. break
  546.  
  547. if bright_correct:
  548. curve = bright.calc_curve_from_buffered_vid(vid)
  549. frame = bright.apply_curve_simple(frame, curve)
  550.  
  551. outputvid.write_frame(frame)
  552.  
  553. # while True:
  554. # retval, frame = vid.read()
  555. # if not retval:
  556. # break
  557.  
  558. # outputvid.write_frame(frame)
  559.  
  560. outputvid.release()
  561. vid.release()
  562.  
  563. import re
  564.  
  565. class TrfLocalMotion:
  566. def __init__(self, dx, dy, x, y):
  567. self.dx = dx
  568. self.dy = dy
  569. self.x = x
  570. self.y = y
  571.  
  572. class TrfFrame:
  573. def __init__(self, idx, local_motions=None):
  574. self.local_motions = local_motions or []
  575. self.idx = idx
  576. # self.is_phantom = is_phantom
  577.  
  578. class TrfFrameCompressed:
  579. def __init__(self, idx, local_motions):
  580. nparr = np.array([[lm.dx, lm.dy, lm.x, lm.y] for lm in local_motions], dtype=int)
  581. self.local_motions_compressed = nparr
  582. self.idx = idx
  583.  
  584. @property
  585. def local_motions(self):
  586. l = []
  587. for dx, dy, x, y in self.local_motions_compressed:
  588. l.append(TrfLocalMotion(dx, dy, x, y))
  589. return l
  590.  
  591. # class TrfCompressedLMSList:
  592. # def __init__(self, local_motions):
  593. # self.size = len(local_motions)
  594. # self.nparr = np.array([[lm.x, lm.y, lm.dx, lm.dy] for lm in local_motions], type=int)
  595.  
  596. # def __len__(self):
  597. # return self.size
  598.  
  599. # def __iter__(self):
  600. # return 0
  601.  
  602. # def __next__(self):
  603. # return 0
  604.  
  605.  
  606. def trf_parser(f):
  607. frame_start_rx = re.compile(r'^Frame ([0-9]+) \(List ([0-9]+) \[')
  608. frame_end_rx = re.compile(r'\]\)$')
  609. lm_rx = re.compile(r'\(LM (\-?[0-9]+) (\-?[0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9\.]+) ([0-9\.]+)\)')
  610. pos = 0
  611.  
  612. def parse_lm(line):
  613. nonlocal pos
  614.  
  615. m = lm_rx.match(line, pos=pos)
  616. if m:
  617. pos += len(m.group(0))
  618. return TrfLocalMotion(
  619. dx=int(m.group(1)),
  620. dy=int(m.group(2)),
  621. x=int(m.group(3)),
  622. y=int(m.group(4)),
  623. )
  624. else:
  625. return None
  626.  
  627. def parse_frame(line):
  628. nonlocal pos
  629.  
  630. m = frame_start_rx.match(line, pos=pos)
  631. if not m:
  632. return None
  633. pos += len(m.group(0))
  634. frame = TrfFrame(int(m.group(1)))
  635. lm_count = int(m.group(2))
  636.  
  637. for lm_idx in range(lm_count):
  638. if lm_idx > 0:
  639. if line[pos] == ',':
  640. pos += 1
  641. else:
  642. return None
  643. lm = parse_lm(line)
  644. if not lm:
  645. return None
  646. frame.local_motions.append(lm)
  647.  
  648. m = frame_end_rx.match(line, pos=pos)
  649. if not m:
  650. return None
  651. return frame
  652.  
  653. first_line = f.readline()
  654. if first_line != 'VID.STAB 1\n':
  655. print('Broken TRF-file, should start VID.STAB 1')
  656. return []
  657. line_idx = 0
  658. frames = []
  659. while True:
  660. line = f.readline()
  661. if not line:
  662. break
  663. pos = 0
  664. line_idx += 1
  665. if line[0] == '#':
  666. continue
  667. frame = parse_frame(line)
  668. if frame is None:
  669. print('Failed to parse TRF-file, line {0}'.format(line_idx))
  670. return []
  671. frame_compressed = TrfFrameCompressed(frame.idx, frame.local_motions)
  672. frames.append(frame_compressed)
  673. return frames
  674.  
  675. def transform_from_trf_lms(local_motions, return_identity_on_fail=True):
  676. if len(local_motions) < 3:
  677. return np.array([[1, 0, 0], [0, 1, 0]], dtype=np.float32) if return_identity_on_fail else None
  678. prev_kps = [[lm.x, lm.y] for lm in local_motions]
  679. cur_kps = [[lm.x + lm.dx, lm.y + lm.dy] for lm in local_motions]
  680. trans, _ = cv2.estimateAffine2D(np.array(prev_kps, dtype=np.float32), np.array(cur_kps, dtype=np.float32), refineIters=20)
  681. if trans is None:
  682. return np.array([[1, 0, 0], [0, 1, 0]], dtype=np.float32) if return_identity_on_fail else None
  683. return trans
  684.  
  685. from scipy import interpolate
  686. import itertools
  687.  
  688. def generate_fake_local_motion_from_transform(transform, size):
  689. x0 = int(0.1 * size[0])
  690. x1 = int(0.9 * size[0])
  691. y0 = int(0.1 * size[1])
  692. y1 = int(0.9 * size[1])
  693. x_points0 = [x0, x0, x1, x1]
  694. y_points0 = [y0, y1, y0, y1]
  695. p1 = transform.dot(np.array([x_points0[0], y_points0[0], 1.0]))
  696. p2 = transform.dot(np.array([x_points0[1], y_points0[1], 1.0]))
  697. p3 = transform.dot(np.array([x_points0[2], y_points0[2], 1.0]))
  698. p4 = transform.dot(np.array([x_points0[3], y_points0[3], 1.0]))
  699. x_points1 = [p1[0], p2[0], p3[0], p4[0]]
  700. y_points1 = [p1[1], p2[1], p3[1], p4[1]]
  701. lms = []
  702. for x_old, y_old, x_new, y_new in zip(x_points0, y_points0, x_points1, y_points1):
  703. lms.append(TrfLocalMotion(x_new - x_old, y_new - y_old, x_new, y_new))
  704. return lms
  705.  
  706. def gen_half_local_motion(lms : List[TrfLocalMotion]):
  707. return [TrfLocalMotion(lm.dx / 2, lm.dy / 2, lm.x, lm.y) for lm in lms]
  708.  
  709. class PtoParser:
  710. @staticmethod
  711. def _parse_pto_image_string(line: str):
  712. assert line[0] == 'i'
  713. m = re.search(r'Vm5 n"([^"]+)"', line)
  714. assert m, "Can't parse PTO image line {0}".format(line)
  715. return m.group(1)
  716.  
  717. @staticmethod
  718. def _parse_pto_kp_string(line: str):
  719. assert line[0] == 'c'
  720. res = {}
  721. for el in line.split(' '):
  722. refIdx = '1' if el[0] == str.lower(el[0]) else '2'
  723. if el == 'c':
  724. pass
  725. elif el[0] == 't':
  726. res['type'] = el[1]
  727. elif str.lower(el[0]) == 'n':
  728. res['img' + refIdx] = int(el[1:])
  729. elif str.lower(el[0]) == 'x':
  730. res['x' + refIdx] = float(el[1:])
  731. elif str.lower(el[0]) == 'y':
  732. res['y' + refIdx] = float(el[1:])
  733. return res
  734.  
  735. @staticmethod
  736. def read_all_images(pto_path):
  737. imgs = []
  738. with open(pto_path, 'r') as f:
  739. for line in f.readlines():
  740. if line[0] == 'i':
  741. parsed_img_name = PtoParser._parse_pto_image_string(line)
  742. imgs.append(parsed_img_name)
  743. return imgs
  744.  
  745. @staticmethod
  746. def parse_transform_from_pto(pto_path, img1_name, img2_name):
  747. img_map = []
  748. imgs = {}
  749. prev_matched_keypoints = []
  750. cur_matched_keypoints = []
  751. with open(pto_path, 'r') as f:
  752. for line in f.readlines():
  753. if line[0] == 'i':
  754. parsed_img_name = PtoParser._parse_pto_image_string(line)
  755. imgs[parsed_img_name] = len(img_map)
  756. img_map.append(parsed_img_name)
  757. elif line[0] == 'c':
  758. ptsLine = PtoParser._parse_pto_kp_string(line)
  759. line_name1 = img_map[ptsLine['img1']]
  760. line_name2 = img_map[ptsLine['img2']]
  761. if line_name1 == img1_name and line_name2 == img2_name:
  762. prev_matched_keypoints.append([ptsLine['x1'], ptsLine['y1']])
  763. cur_matched_keypoints.append([ptsLine['x2'], ptsLine['y2']])
  764. elif line_name1 == img2_name and line_name2 == img1_name:
  765. cur_matched_keypoints.append([ptsLine['x1'], ptsLine['y1']])
  766. prev_matched_keypoints.append([ptsLine['x2'], ptsLine['y2']])
  767.  
  768. assert img1_name in imgs, '{0} not found in {1}'.format(img1_name, pto_path)
  769. assert img2_name in imgs, '{0} not found in {1}'.format(img2_name, pto_path)
  770.  
  771. transform, inliers = cv2.estimateAffine2D(np.array(cur_matched_keypoints), np.array(prev_matched_keypoints))
  772. # if transform is None:
  773. cur_kps = np.array(cur_matched_keypoints)
  774. prev_kps = np.array(prev_matched_keypoints)
  775. dx = np.mean(prev_kps[:,0] - cur_kps[:,0])
  776. dy = np.mean(prev_kps[:,1] - cur_kps[:,1])
  777. transform = np.array([[1, 0, dx], [0, 1, dy]])
  778. # print('TWO TRANSFORMS', transform, transform1)
  779. # print(len(inliers))
  780. # print(transform)
  781. return transform
  782.  
  783. class PtoFilesLib:
  784. def __init__(self, pto_files: List[str]):
  785. self.img_table = {}
  786. if pto_files is None:
  787. return
  788. file_list = []
  789. for pto_file_path in pto_files:
  790. if os.path.isdir(pto_file_path):
  791. for fname in os.listdir(pto_file_path):
  792. _, ext = os.path.splitext(fname)
  793. pto_path_in_dir = os.path.join(pto_file_path, fname)
  794. if ext == '.pto' and os.path.isfile(pto_path_in_dir):
  795. file_list.append(pto_path_in_dir)
  796. else:
  797. file_list.append(pto_file_path)
  798. print(file_list)
  799. for pto_file_path in file_list:
  800. if os.path.exists(pto_file_path):
  801. all_images = PtoParser.read_all_images(pto_file_path)
  802. for img in all_images:
  803. self.img_table[img] = pto_file_path
  804.  
  805. def frame_idx_name(self, idx: int):
  806. return f'frame{idx}.png'
  807.  
  808. def in_table(self, frame_idx):
  809. return self.frame_idx_name(frame_idx) in self.img_table
  810.  
  811. def transform_in_table(self, frame_idx):
  812. return self.in_table(frame_idx) and self.in_table(frame_idx - 1)
  813.  
  814. def get_transform_for_image(self, frame_idx1, frame_idx2):
  815. img1_name = self.frame_idx_name(frame_idx1)
  816. img2_name = self.frame_idx_name(frame_idx2)
  817. return PtoParser.parse_transform_from_pto(self.img_table[img1_name], img2_name, img1_name)
  818.  
  819.  
  820. from collections import deque
  821.  
  822. def detect_phantom_frames(frames):
  823. sliding_window_size = 7
  824.  
  825. def vec_xy(trans):
  826. return np.array((trans[0, 2], trans[1, 2]))
  827.  
  828. def window(seq, n=2):
  829. it = iter(seq)
  830. win = deque((next(it, None) for _ in range(n)), maxlen=n)
  831. yield np.array(win)
  832. append = win.append
  833. for e in it:
  834. append(e)
  835. yield np.array(win)
  836.  
  837. frames_mov = map(lambda frm: vec_xy(transform_from_trf_lms(frm.local_motions)), frames)
  838. i = sliding_window_size // 2
  839. phantom_frames = []
  840. for w in window(frames_mov, n=sliding_window_size):
  841. mid = sliding_window_size // 2
  842. v = w[mid]
  843. vabs = np.linalg.norm(v)
  844. varound = np.concatenate((w[0:mid], w[mid+1:]))
  845. vavg = np.average(varound, axis=0)
  846. vstd = np.std(np.linalg.norm(varound, axis=1))
  847. acceptable_speed = np.linalg.norm(vavg) + 4 * vstd
  848.  
  849. # строим сглаживающую кривую
  850. # x_std = np.std(ay) # TODO: нужно считать стандартное отклонение от сглаживающей кривой
  851. # y_std = np.std(ax)
  852. # num_points = len(good_trans)
  853. # k = 1 # TODO: если num_point = 1? в этом случае мы должны записать 1, так как не можем интерполировать
  854. # xs = np.concatenate((np.arange(-sliding_window_size // 2 + 1, 0), np.arange(1, sliding_window_size // 2 + 1)))
  855. # ys = varound
  856. # spl_x = interpolate.splrep(xs, ys[:, 0], k=3, s=sliding_window_size * np.std(ys[:, 0])**2)
  857. # spl_y = interpolate.splrep(xs, ys[:, 1], k=3, s=sliding_window_size * np.std(ys[:, 1])**2)
  858. # vinterp = np.transpose(np.array((interpolate.splev(xs, spl_x), interpolate.splev(xs, spl_y))))
  859.  
  860. # 1. Абсолютная скорость больше 10
  861. # 2. скоерость больше среднего + 4 * отклонения
  862. if vabs > 10 and np.linalg.norm(2 * vavg - v) < 0.2 * vabs and vabs > acceptable_speed:
  863.  
  864. print(f'speed: {vabs}')
  865. print(f'neighbors speed: {np.linalg.norm(w, axis=1)}')
  866. print(f'similar to 2 * avg: {np.linalg.norm(2 * vavg - v)} < {0.2 * vabs}')
  867. print(f'higher than acceptable: {vabs} > {acceptable_speed}')
  868. print(f'v[mid] > 1.6 * min(v[mid - 1], min[mid + 1]): {vabs} > {acceptable_speed}', )
  869.  
  870. phantom_frames.append(i)
  871.  
  872. i += 1
  873. # phantom_frames = sorted(phantom_frames + [63 + 6794-74+1, 72 + 6794-74+1, 8782 -1 ]) # 15722
  874. # phantom_frames = phantom_frames[:-1]
  875. print('phanotm frames', phantom_frames)
  876. return phantom_frames
  877.  
  878. # for frm in frames:
  879. # s = [3884, 6016, 13268, 13395, 14088]
  880.  
  881. # у bad_run-ов приоритет над whitelist
  882. def filter_trf(frames, resolution, initial_bad_runs=None, run_whitelist=None, phantom_frames=[], pto_files=None):
  883. last_bad_run = None
  884. bad_runs = []
  885. run_whitelist = run_whitelist or []
  886. pto_lib = PtoFilesLib(pto_files)
  887.  
  888. def in_good_runs(frmidx):
  889. nonlocal run_whitelist
  890. for run in run_whitelist:
  891. if frmidx >= run[0] and frmidx <= run[1]:
  892. return True
  893. return False
  894.  
  895. def in_initial_bad_runs(frmidx):
  896. nonlocal initial_bad_runs
  897. for run in initial_bad_runs:
  898. if frmidx >= run[0] and frmidx <= run[1]:
  899. return True
  900. return False
  901.  
  902. def in_bad_runs(frmidx):
  903. nonlocal bad_runs
  904. for run in bad_runs:
  905. if frmidx >= run[0] and frmidx <= run[1]:
  906. return True
  907. return False
  908.  
  909. for frm in frames:
  910. LOCAL_MOTIONS_THRESHOLD = 100
  911. # TODO: поставил threhosld на 5, чтобы отключить.
  912. # Проблема в следующем - он отключает стабилизацию как раз в тот момент, когда она нужнее всего.
  913. if (len(frm.local_motions) < LOCAL_MOTIONS_THRESHOLD and frm.idx > 1 and not in_good_runs(frm.idx)
  914. and not pto_lib.transform_in_table(frm.idx)) or in_initial_bad_runs(frm.idx):
  915. # TODO: Очень плохой код!!
  916. if last_bad_run is None:
  917. last_bad_run = [frm.idx, frm.idx]
  918. elif frm.idx - last_bad_run[1] <= 2:
  919. last_bad_run[1] = frm.idx
  920. else:
  921. bad_runs.append(last_bad_run)
  922. last_bad_run = [frm.idx, frm.idx]
  923.  
  924. if last_bad_run:
  925. bad_runs.append(last_bad_run)
  926.  
  927. filtered_bad_runs = []
  928.  
  929. # Замяем плохую компенсацию предсказанным движением
  930. # То есть фактически выключаем стабилизатор
  931. # TODO: Заменить сплайны на гаусса.
  932. for bad_run in bad_runs:
  933. trans_good = transform_from_trf_lms(frames[bad_run[0] - 2].local_motions)
  934. trans = transform_from_trf_lms(frames[bad_run[0] - 1].local_motions)
  935. if bad_run[1] >= len(frames) - 3:
  936. trans_good_end = trans_good
  937. else:
  938. trans_good_end = transform_from_trf_lms(frames[bad_run[1]].local_motions)
  939. extrap_size = 15 # учитываем 15 кадров до и после
  940. good_frame_indices = []
  941. good_trans = []
  942.  
  943. for good_frame_idx in itertools.chain(
  944. range(max((0, bad_run[0] - extrap_size - 1)), bad_run[0] - 1),
  945. range(bad_run[1] + 2, min((bad_run[1] + extrap_size + 1, len(frames) - 1)))):
  946.  
  947. # if not in_initial_bad_runs(good_frame_idx) and not in_bad_runs(good_frame_idx - 1):
  948. trans = transform_from_trf_lms(frames[good_frame_idx - 1].local_motions)
  949. if np.isfinite(trans).all():
  950. good_frame_indices.append(good_frame_idx)
  951. good_trans.append(trans)
  952.  
  953. # Получаем сплайны
  954. ax = np.array(good_trans)[:, 0, 2]
  955. ay = np.array(good_trans)[:, 1, 2]
  956. x_std = np.std(ay) # TODO: нужно считать стандартное отклонение от сглаживающей кривой
  957. y_std = np.std(ax)
  958. num_points = len(good_trans)
  959. k = 1 # TODO: если num_point = 1? в этом случае мы должны записать 1, так как не можем интерполировать
  960. spl_x = interpolate.splrep(good_frame_indices, ax, k=k, s=num_points * x_std**2)
  961. spl_y = interpolate.splrep(good_frame_indices, ay, k=k, s=num_points * y_std**2)
  962.  
  963. bad_indices = list(range(bad_run[0], bad_run[1] + 1))
  964. x_interpolated = interpolate.splev(bad_indices, spl_x)
  965. y_interpolated = interpolate.splev(bad_indices, spl_y)
  966.  
  967. bad_trans = []
  968. for bad_frame_idx in range(bad_run[0], bad_run[1] + 1):
  969. bad_trans.append(transform_from_trf_lms(frames[bad_frame_idx - 1].local_motions))
  970. bad_ax = np.array(bad_trans)[:, 0, 2]
  971. bad_ay = np.array(bad_trans)[:, 1, 2]
  972.  
  973. outliers = np.logical_or(np.absolute(x_interpolated - bad_ax) > 2 * x_std, np.absolute(y_interpolated - bad_ay) > 2 * y_std)
  974. print(outliers)
  975.  
  976. print(bad_run[0], trans_good[0, 2], trans_good[1, 2], trans[0, 2], trans[1, 2], trans_good_end[0, 2], trans_good_end[1, 2])
  977. if np.any(outliers):
  978. filtered_bad_runs.append([bad_run, spl_x, spl_y])
  979.  
  980. # Удаляем плохие кадры, заменяем их на предсказанное
  981. if len(filtered_bad_runs) > 0:
  982.  
  983. print(np.array(filtered_bad_runs, dtype=object)[:, 0])
  984.  
  985. filtered_frames = []
  986. cur_bad_run_idx = 0
  987. for frm in frames:
  988. if frm.idx > filtered_bad_runs[cur_bad_run_idx][0][1] and cur_bad_run_idx < len(filtered_bad_runs) - 1:
  989. cur_bad_run_idx += 1
  990. cur_bad_run = filtered_bad_runs[cur_bad_run_idx]
  991. if pto_lib.transform_in_table(frm.idx) and not in_initial_bad_runs(frm.idx):
  992. pto_trans = pto_lib.get_transform_for_image(frm.idx, frm.idx - 1)
  993. print(f'IN TABLE {frm.idx}')
  994. fake_lms = generate_fake_local_motion_from_transform(pto_trans, resolution)
  995. filtered_frames.append(TrfFrame(frm.idx, fake_lms))
  996. elif frm.idx >= cur_bad_run[0][0] and frm.idx <= cur_bad_run[0][1]:
  997. spl_x = cur_bad_run[1]
  998. spl_y = cur_bad_run[2]
  999. x_interpolated = interpolate.splev([frm.idx], spl_x)[0]
  1000. y_interpolated = interpolate.splev([frm.idx], spl_y)[0]
  1001. fake_transform = np.float32([[1, 0, x_interpolated], [0, 1, y_interpolated]])
  1002. fake_lms = generate_fake_local_motion_from_transform(fake_transform, resolution)
  1003. filtered_frames.append(TrfFrame(frm.idx, fake_lms))
  1004. else:
  1005. filtered_frames.append(frm)
  1006. else:
  1007. filtered_frames = frames.copy()
  1008.  
  1009. # добавляем фантомные кадры
  1010. for frmidx in reversed(sorted(phantom_frames)):
  1011. frmidx0 = frmidx + 1
  1012. frmidx1 = frmidx0 - 1
  1013.  
  1014. frm = filtered_frames[frmidx1]
  1015. half_motion = gen_half_local_motion(frm.local_motions)
  1016. # half_motion = frm.local_motions
  1017.  
  1018. filtered_frames[frmidx1] = TrfFrame(frmidx0, half_motion)
  1019. filtered_frames.insert(frmidx1, TrfFrame(frmidx0, half_motion))
  1020.  
  1021. for frm in filtered_frames[frmidx1 + 1:]:
  1022. frm.idx += 1
  1023.  
  1024. return filtered_frames
  1025.  
  1026. def write_trf(f, frames):
  1027. f.write('VID.STAB 1\n# write_trf()\n')
  1028. for frame in frames:
  1029. lm_str_list = []
  1030. if hasattr(frame, 'local_motions_compressed'):
  1031. for dx, dy, x, y in frame.local_motions_compressed:
  1032. kp_str = '(LM {0} {1} {2} {3} 112 0.5 0.5)'.format(dx, dy, x, y)
  1033. lm_str_list.append(kp_str)
  1034. else:
  1035. for lm in frame.local_motions:
  1036. kp_str = '(LM {0} {1} {2} {3} 112 0.5 0.5)'.format(
  1037. probabilistic_round(lm.dx),
  1038. probabilistic_round(lm.dy),
  1039. probabilistic_round(lm.x),
  1040. probabilistic_round(lm.y))
  1041. lm_str_list.append(kp_str)
  1042. f.write('Frame {0} (List {1} [{2}])\n'.format(frame.idx, len(lm_str_list), ','.join(lm_str_list)))
  1043.  
  1044.  
  1045. def main():
  1046. parser = argparse.ArgumentParser(description='Stabilizer for figure skating.')
  1047. parser.add_argument('-f', help='Input path', dest='input_path', type=str, required=True)
  1048. parser.add_argument('-trf', help='Trf output path', dest='trf_path', type=str)
  1049. parser.add_argument('-o', help='Output video', dest='output_path', type=str)
  1050. parser.add_argument('-q', help='Output quality (CRF, 0-lossless, 51-worst possible)', dest='quality', default=9, type=int)
  1051. args = parser.parse_args()
  1052. trf_path = args.trf_path or args.input_path + '.trf'
  1053. output_path = args.output_path or args.input_path + '.stabilized.nosound.mp4'
  1054.  
  1055. smooth_trajectory = None
  1056. camera_trajectory, stats = gen_motion_vectors(args.input_path, True, trf_path)
  1057. smooth_trajectory = trajectory_smoother(camera_trajectory)
  1058. render(args.input_path, output_path, trf_path, smooth_trajectory, args.quality)
  1059.  
  1060. import os
  1061.  
  1062. def decompose_transition_into_scalexy_shearx_rotate(a):
  1063. sx = math.sqrt(a[0][0]**2 + a[1][0]**2)
  1064. theta = math.tan(a[1][0] / a[0][0])
  1065. msy = a[0][1] * math.cos(theta) + a[1][1] * math.sin(theta)
  1066. sy = (msy * math.cos(theta) - a[0][1]) / math.sin(theta)
  1067. m = msy / sy
  1068. return theta, sx, sy, m
  1069.  
  1070. from scipy import stats
  1071.  
  1072. # Поиск дропнутых кадров
  1073. def search_drops_in_trf(frames):
  1074. transforms = []
  1075. for frm in frames[1:]:
  1076. transforms.append(transform_from_trf_lms(frm.local_motions))
  1077. ntransforms = np.array(transforms)
  1078. dx = ntransforms[:, 0, 2]
  1079. dy = ntransforms[:, 1, 2]
  1080.  
  1081.  
  1082.  
  1083. def analyze_trf(frames):
  1084. zoomy = []
  1085. dx = []
  1086. dy = []
  1087. shearx = []
  1088. last_trans = None
  1089. for frm in frames[1:]:
  1090. trans = transform_from_trf_lms(frm.local_motions)
  1091. theta, sx, sy, m = decompose_transition_into_scalexy_shearx_rotate(trans)
  1092. rot = np.float32([[math.cos(theta), -math.sin(theta)], [math.sin(theta), math.cos(theta)]])
  1093. shear = np.float32([[1, m], [0, 1]])
  1094. scale = np.float32([[sx, 0], [0, sy]])
  1095. res = np.matmul(np.matmul(rot, shear), scale)
  1096. if last_trans is not None:
  1097. zoomy.append(sy / sx)
  1098. dx.append(trans[0, 2] - last_trans[0, 2])
  1099. dy.append(trans[1, 2] - last_trans[1, 2])
  1100. shearx.append(m)
  1101. last_trans = trans
  1102. # print(theta, sy/sx, m, trans[0:2, 0:2], res)
  1103. with np.errstate(invalid='ignore'):
  1104. good1 = np.logical_and(np.logical_and(np.array(dy) > -100, np.array(dy) < 100), np.logical_and(np.array(zoomy) > 0.8, np.array(zoomy) < 1.2))
  1105. good2 = np.logical_and(np.logical_and(np.array(dx) > -100, np.array(dx) < 100), np.logical_and(np.array(shearx) > -0.5, np.array(shearx) < 0.5))
  1106. slope, intercept, _, _, _ = stats.linregress(np.array(dy)[good1], np.array(zoomy)[good1])
  1107. slope1, intercept1, _, _, _ = stats.linregress(np.array(dx)[good2], np.array(shearx)[good2])
  1108. print(slope * 1080, intercept * 1080 - 1080)
  1109. print(slope1 * 1920, intercept1 * 1920)
  1110. xi = np.linspace(-100, 100)
  1111. # plt.plot(xi, (slope * xi + intercept) * 1080 - 1080)
  1112. # plt.plot(dy, np.array(zoomy) * 1080 - 1080, linestyle="",marker=".")
  1113. # plt.plot(xi, (slope1 * xi + intercept1) * 1920)
  1114. # plt.plot(dx, np.array(shearx) * 1920, linestyle="",marker=".")
  1115. # plt.show()
  1116.  
  1117. # Выделим отдельную задачу - трекинг фигуриста.
  1118.  
  1119. # Файлы, которые нужно стабилизировать
  1120. FILES_TO_STAB = [
  1121. # ['/media/vlad/new2021/editins/nuga-evalotta/DSCF0017.MOV', {'bad_runs': [[2652, 2704], [3593, 3602], [6370, 6396], [8749, 8785], [9034, 9062], [10438, 10461],
  1122. # [12990, 13028], [15542, 15619], [15413, 15452], [15995, 16053], [18480, 18515], [21019, 21090], [21247, 21281], [21265, 21490],
  1123. # [21505, 21606], [25215, 25245]]}],
  1124. # ['/media/vlad/new2021/editins/nuga-evalotta/DSCF0015.MOV', {'bad_runs': [[15741, 15793], [18706, 18737], [20678, 20909], [21013, 21064]] }],
  1125. # ['/media/vlad/new2021/editins/nuga-evalotta/DSCF0016.MOV', {'bad_runs': [[3750, 3862]]}],
  1126. '/media/vlad/new2021/editins/nuga-evalotta/DSCF6876.MOV',
  1127.  
  1128. '/media/vlad/new2021/sdcards/jumpfest2020/day3/sdcard3/DCIM/100_FUJI/DSCF0006.MOV',
  1129. ]
  1130.  
  1131. def main1():
  1132. quality = 18
  1133.  
  1134. # with open('/media/vlad/new2021/editins/sasha/sinitsa/dedups_3.json', 'r') as f:
  1135. # duplicate_table = json.load(f)
  1136. duplicate_table = {}
  1137.  
  1138. for input_path in FILES_TO_STAB:
  1139.  
  1140. if not isinstance(input_path, str):
  1141. input_params = input_path[1]
  1142. input_path = input_path[0]
  1143. else:
  1144. input_params = {}
  1145.  
  1146. preset_good_runs = input_params.get('good_runs', [])
  1147. preset_bad_runs = input_params.get('bad_runs', [])
  1148. phantom_frames = input_params.get('phantom_frames', [])
  1149. pto_files = input_params.get('pto_files', [])
  1150. smoothness = input_params.get('smoothness', 20)
  1151.  
  1152. trf_path = input_path + '.trf'
  1153. filtered_trf_path = input_path + '.filtered.trf'
  1154. output_path = input_path + '.stabilized.nosound.mp4'
  1155. resolution = (width, height)
  1156.  
  1157.  
  1158. if os.path.exists(output_path):
  1159. print('{0} already exists skipping'.format(output_path))
  1160. continue
  1161. if not os.path.exists(trf_path):
  1162. dup_set = duplicate_table.get(input_path, None)
  1163. camera_trajectory, stats = gen_motion_vectors(input_path, True, trf_path, dup_set)
  1164. print(stats)
  1165. if os.path.exists(trf_path):
  1166. print('{0} already exists, let\'s filter it'.format(trf_path))
  1167. with open(trf_path, 'r') as f:
  1168. parsed_trf = trf_parser(f)
  1169. # analyze_trf(parsed_trf)
  1170. # phantom_frames = []
  1171. if phantom_frames == 'detect':
  1172. phantom_frames = detect_phantom_frames(parsed_trf)
  1173.  
  1174. filtered_trf = filter_trf(parsed_trf, resolution, initial_bad_runs=preset_bad_runs, run_whitelist=preset_good_runs,
  1175. phantom_frames=phantom_frames,
  1176. pto_files=pto_files,
  1177. )
  1178. # pto_files=['/media/vlad/new2020/fskating/gprusproj/sasha/DSCF0026.MOV.pto/frame9875 - frame18572.pto',
  1179. # '/media/vlad/new2020/fskating/gprusproj/sasha/DSCF0026.MOV.pto/frame12073 - frame12076.pto'])
  1180. # filtered_trf = parsed_trf
  1181. with open(filtered_trf_path, 'w') as f:
  1182. write_trf(f, filtered_trf)
  1183. trajectory = [FrameInfo(None, None, None) for x in filtered_trf[1:]]
  1184. del parsed_trf
  1185. del filtered_trf
  1186. for phantom_idx, frmidx in enumerate(sorted(phantom_frames)):
  1187. trajectory[frmidx + phantom_idx - 1].is_bad_frame = True
  1188. render(input_path, output_path, filtered_trf_path, trajectory, quality, smoothness=smoothness)
  1189. else:
  1190. smooth_trajectory = None
  1191. smooth_trajectory = trajectory_smoother(camera_trajectory)
  1192. render(input_path, output_path, trf_path, smooth_trajectory, quality)
  1193. # render(input_path, output_path, trf_path, [], quality)
  1194.  
  1195.  
  1196. # def extract_all_bad_frags():
  1197.  
  1198.  
  1199. main1()
  1200. # extract_all_bad_frags()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement