def handle(self, video_path: str) -> bool: super(KerasHandler, self).handle(video_path) video = VideoObject(video_path) if self.preload: video.load_frames() # --- cutter --- cutter = VideoCutter() res = cutter.cut(video) stable, unstable = res.get_range(threshold=0.98, offset=3) # --- classify --- cl = KerasClassifier() if self.model_path: logger.info("load existed pre-train model") cl.load_model(self.model_path) else: data_home = res.pick_and_save(stable, self.frame_count, to_dir=self.result_path) cl.train(data_home) self.classifier_result = cl.classify(video, stable) # --- draw --- r = Reporter() r.draw(self.classifier_result, report_path=self.result_report_path) return True
def test_boost(): video = VideoObject(VIDEO_PATH) video.load_frames() # test cut res, data_home = _cut(video) # test classify classify_result = _classify(video, data_home) # --- draw --- r = Reporter() r.draw( classify_result, report_path=os.path.join(data_home, "report.html"), cut_result=res, ) # test compressing r = Reporter() r.draw( classify_result, report_path=os.path.join(data_home, "report.html"), cut_result=res, compress_rate=0.1, ) r = Reporter() r.draw( classify_result, report_path=os.path.join(data_home, "report.html"), cut_result=res, target_size=(600, 800), )
def test_read_from_mem(): v = VideoObject(VIDEO_PATH) v.load_frames() count = 0 for f in v: assert isinstance(f, VideoFrame) count += 1 assert count == 30
def test_read_from_mem(): v = VideoObject(VIDEO_PATH) print(str(v)) v.load_frames() count = 0 for f in v: assert isinstance(f, VideoFrame) print(str(f)) count += 1 assert count == 30 v.clean_frames() assert not v.data
def test_boost(): video = VideoObject(VIDEO_PATH) video.load_frames() # test cut res, data_home = _cut(video) # test classify classify_result = _classify(video, data_home) # --- draw --- r = Reporter() r.draw( classify_result, report_path=os.path.join(data_home, "report.html"), cut_result=res, )
def handle(self, video_path: str) -> bool: super(NormalHandler, self).handle(video_path) video = VideoObject(video_path) if self.preload: video.load_frames() # --- cutter --- cutter = VideoCutter() res = cutter.cut(video) stable, unstable = res.get_range(threshold=0.98, offset=3) data_home = res.pick_and_save(stable, self.frame_count, to_dir=self.result_path) # --- classify --- cl = SVMClassifier() cl.load(data_home) cl.train() self.classifier_result = cl.classify(video, stable) # --- draw --- r = Reporter() r.draw(self.classifier_result, report_path=self.result_report_path) return True
from stagesepx.cutter import VideoCutter from stagesepx.classifier import SVMClassifier from stagesepx.reporter import Reporter from stagesepx.video import VideoObject video_path = "../videos/long.mp4" video = VideoObject(video_path) video.load_frames() # --- cutter --- cutter = VideoCutter() res = cutter.cut(video) stable, unstable = res.get_range() data_home = res.pick_and_save(stable, 5) # --- classify --- cl = SVMClassifier(compress_rate=0.4) cl.load(data_home) cl.train() classify_result = cl.classify(video, stable, keep_data=True) result_dict = classify_result.to_dict() # --- draw --- r = Reporter() r.draw(classify_result)
def test_preload_with_hook(): v = VideoObject(VIDEO_PATH) hook = ExampleHook() v.add_preload_hook(hook) v.load_frames()
def test_contain_image(): v = VideoObject(VIDEO_PATH) v.load_frames() ret = v.data[0].contain_image(image_path=IMAGE_PATH) assert ret["ok"]
def test_convert_first(): v = VideoObject(VIDEO_PATH, fps=30) v.load_frames() assert len(v.data) == 36
def get_range(_train_or_forecast, _forecast_file_name, _param, _picture_path): file_name = re.search(r'\\(.*).mp4', str(_forecast_file_name), re.M | re.I).group(1) video = VideoObject(_forecast_file_name, pre_load=True) # 新建帧,计算视频总共有多少帧,每帧多少ms video.load_frames() # 直接切割视频 # 压缩视频 cutter = VideoCutter(compress_rate=_param[0]) # 添加Hook ''' convert_size_and_offset:1zai 51 - origin size: ((468, 216)) 显示的是整张图的尺寸,坐标原点在左上角。 468是高,216是宽。(468, 216)是整个画面的右下角 stagesepx.hook:convert_size_and_offset:153 - size: (23.4, 216) 显示的是被屏蔽或者是被选择的区域的高和宽。高是23.4,宽是216。 convert_size_and_offset:160。final range h: (0, 23), w: (0, 216) 显示的是被屏蔽或者是被选择的区域。 从左上角开始计算,屏蔽掉0到23高度,整个宽度的区域。 如果要统计竖屏切换到横屏场景的耗时。为了防止录屏文件中,横屏后的"五日分数图"内容被压缩的太小,建议在录屏时选择横屏录制。 横屏录制场景下,某一帧图像中,App的内容处于竖屏状态下时,该帧图像表现为,左边一部分黑屏,中间是手机竖屏页面,右边还是一部分黑屏。 此时如果要屏蔽或者选择某一块区域,要把黑色区域的尺寸也算在内。 如果只通过中间部分有手机内容图像的尺寸,来选择或者屏蔽部分区域,则会出现被选择的区域和预期不符的情况。 # 屏蔽盘口数据 size=(0.1, 1), # 屏蔽手机导航栏 size=(0.05, 1), ''' ''' # 屏蔽帧的右边 hook_ignore1 = IgnoreHook( size=(1, 1), offset=(0, 0.55), overwrite=True, ) # 屏蔽帧的左边 hook_ignore2 = IgnoreHook( size=(1, 0.45), overwrite=True, ) # 屏蔽帧的上边 hook_ignore3 = IgnoreHook( size=(0.35, 1), overwrite=True, ) # 屏蔽帧的下边 hook_ignore4 = IgnoreHook( size=(1, 1), offset=(0.6, 0), overwrite=True, ) hook_crop = CropHook( size=(0.2, 0.2), overwrite=True, ) ''' # cutter.add_hook(hook_ignore1) # cutter.add_hook(hook_ignore2) # cutter.add_hook(hook_ignore3) # cutter.add_hook(hook_ignore4) # cutter.add_hook(hook_crop) # hook_save_frame = FrameSaveHook('../frame_save_dir') # cutter.add_hook(hook_save_frame) # 计算每一帧视频的每一个block的ssim和psnr。block=4则算16个part的得分 # res = cutter.cut(video) res = cutter.cut(video, block=_param[3]) # 计算出哪些区间是稳定的,哪些是不稳定的。判断A帧到B帧之间是稳定还是不稳定 # 是不是在这里就决定,把稳定的A到B帧,放到一个文件夹。如果offset大,就扩大A到B的间隔。 stable, unstable = res.get_range(threshold=_param[1], offset=_param[2]) # stable, unstable = res.get_range(threshold=_param[1], offset=_param[2], limit=5,) # stable, unstable = res.get_range(threshold=_param[1], offset=_param[2], psnr_threshold=0.85) # stable, unstable = res.get_range(threshold=_param[1]) if _train_or_forecast == 'train': print("pick_and_save") # 把分好类的稳定阶段的图片存本地 res.pick_and_save(stable, 20, to_dir=_picture_path + '/train_stable_' + file_name, meaningful_name=True) # 把分好类的不稳定阶段的图片存本地 res.pick_and_save(unstable, 40, to_dir=_picture_path + '/train_unstable_' + file_name, meaningful_name=True) else: res.pick_and_save(stable, 20, to_dir=_picture_path + '/forecast_stable_' + file_name, meaningful_name=True) # 把分好类的不稳定阶段的图片存本地 res.pick_and_save(unstable, 40, to_dir=_picture_path + '/forecast_unstable_' + file_name, meaningful_name=True) return stable
def test_sync_timestamp(): v = VideoObject(VIDEO_PATH) v.load_frames() v.sync_timestamp()
def run(config: typing.Union[dict, str]): """ run with config :param config: config file path, or a preload dict :return: """ class _VideoUserConfig(BaseModel): path: str pre_load: bool = True fps: int = None class _CutterUserConfig(BaseModel): threshold: float = None frame_count: int = None offset: int = None limit: int = None block: int = None # common compress_rate: float = None target_size: typing.Tuple[int, int] = None class _ClassifierType(Enum): SVM = "svm" KERAS = "keras" class _ClassifierUserConfig(BaseModel): boost_mode: bool = None classifier_type: _ClassifierType = _ClassifierType.SVM model: str = None # common compress_rate: float = None target_size: typing.Tuple[int, int] = None class _CalcOperatorType(Enum): BETWEEN = "between" DISPLAY = "display" class _CalcOperator(BaseModel): name: str calc_type: _CalcOperatorType args: dict = dict() class _CalcUserConfig(BaseModel): output: str = None ignore_error: bool = None operators: typing.List[_CalcOperator] = None class _ExtraUserConfig(BaseModel): save_train_set: str = None class UserConfig(BaseModel): output: str video: _VideoUserConfig cutter: _CutterUserConfig = _CutterUserConfig() classifier: _ClassifierUserConfig = _ClassifierUserConfig() calc: _CalcUserConfig = _CalcUserConfig() extras: _ExtraUserConfig = _ExtraUserConfig() if isinstance(config, str): # path config_path = pathlib.Path(config) assert config_path.is_file(), f"no config file found in {config_path}" # todo: support different types in the future assert config_path.as_posix().endswith( ".json"), "config file should be json format" with open(config_path, encoding=constants.CHARSET) as f: config = json.load(f) config = UserConfig(**config) logger.info(f"config: {config}") # main flow video = VideoObject( # fmt: off path=config.video.path, fps=config.video.fps, ) if config.video.pre_load: video.load_frames() # cut cutter = VideoCutter( # fmt: off compress_rate=config.cutter.compress_rate, target_size=config.cutter.target_size, ) res = cutter.cut( # fmt: off video=video, block=config.cutter.block, ) stable, unstable = res.get_range( # fmt: off threshold=config.cutter.threshold, offset=config.cutter.offset, ) with tempfile.TemporaryDirectory() as temp_dir: # classify if config.classifier.classifier_type is _ClassifierType.SVM: cl = SVMClassifier( # fmt: off compress_rate=config.classifier.compress_rate, target_size=config.classifier.target_size, ) elif config.classifier.classifier_type is _ClassifierType.KERAS: from stagesepx.classifier.keras import KerasClassifier cl = KerasClassifier( # fmt: off compress_rate=config.classifier.compress_rate, target_size=config.classifier.target_size, ) # validation has been applied by pydantic # so no `else` if config.classifier.model: # no need to retrain model_path = pathlib.Path(config.classifier.model) assert model_path.is_file(), f"file {model_path} not existed" cl.load_model(model_path) else: # train a new model train_set_dir = config.extras.save_train_set or temp_dir os.makedirs(train_set_dir, exist_ok=True) res.pick_and_save( # fmt: off stable, frame_count=config.cutter.frame_count, to_dir=train_set_dir, ) cl.train(data_path=train_set_dir) # start classifying classify_result = cl.classify( # fmt: off video, stable, boost_mode=config.classifier.boost_mode, ) # calc def _calc_display() -> dict: # jsonify return json.loads(classify_result.dumps()) def _calc_between(*, from_stage: str = None, to_stage: str = None) -> dict: assert classify_result.contain( from_stage), f"no stage {from_stage} found in result" assert classify_result.contain( to_stage), f"no stage {to_stage} found in result" from_frame = classify_result.last(from_stage) to_frame = classify_result.first(to_stage) cost = to_frame.timestamp - from_frame.timestamp return { "from": from_frame.frame_id, "to": to_frame.frame_id, "cost": cost, } _calc_func_dict = { _CalcOperatorType.BETWEEN: _calc_between, _CalcOperatorType.DISPLAY: _calc_display, } calc_output = config.calc.output if calc_output: output_path = pathlib.Path(calc_output) assert not output_path.is_file(), f"file {output_path} already existed" result = [] for each_calc in config.calc.operators: func = _calc_func_dict[each_calc.calc_type] try: func_ret = func(**each_calc.args) except Exception as e: if not config.calc.ignore_error: raise logger.warning(e) func_ret = traceback.format_exc() calc_ret = { "name": each_calc.name, "type": each_calc.calc_type.value, "result": func_ret, } result.append(calc_ret) with open(output_path, "w", encoding=constants.CHARSET) as f: json.dump(result, f) # draw r = Reporter() r.draw( # fmt: off classify_result, report_path=config.output, )