简介
由于自己没事在平台上发一些视频,但是每次都感觉写文案和剪辑最费时间。文案来感觉了一下子就出来了,但是剪辑视频是真废时间啊。于是想个办法能不能先自动把视频剪短,然后根据文案直接丢到某映里对应一下就好了。要求也不高只要把场景不同的分割开就好了!(全部代码会放在最后!)
方法
开始本来想着找个现成的改一改,但是基本都是不可用的,有自动剪辑的都是商业化了不可能给你源码对吧。于是根据自己的经验想了一个比较暴力简单的方法。
下列图片可以看出,场景不同场景相似度很低下列两张图相似度只有:0.34
:
而场景比较连续并且相近帧数的图片相似度很高,下列图片相似度:0.85
如下:
所以根据以上特征我制定了如下步骤:
- 逐帧切割图片进行保存:
def cut_video(video):
cap = cv2.VideoCapture(video)
frame_count = 0
while True:
dir_num = frame_count // 2000
cut_save_dir = f"{video.replace('.', '_')}/{dir_num}"
if not os.path.exists(cut_save_dir):
os.makedirs(cut_save_dir)
ret, frame = cap.read()
if not ret:
break
frame_name = os.path.join(cut_save_dir, f'{frame_count}.png')
cv2.imwrite(frame_name, frame)
frame_count += 1
print(frame_name)
cap.release()
return f"切割结束地址为:'{cut_save_dir}' \n 共{frame_count}张图片, 请检查!"
- 抽取每一帧图片的特征,这里我用了
resnet50
:
#尽量用显卡,cpu太慢
def exctract_resnet50(pic_dir):
model = resnet50(weights=ResNet50_Weights.DEFAULT)
model.eval()
model = torch.nn.Sequential(*list(model.children())[:-1]).cuda(0)
to_tensor = transforms.Compose([transforms.Resize((512, 512)),
# transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))
])
imgs_list = sorted(glob(f"{pic_dir}/*/*"), key=int)
fetures, paths = [], []
for img_item in tqdm(imgs_list):
# print(img_item)
img = Image.open(img_item)
img_tensor = to_tensor(img)
with torch.no_grad():
res = model(img_tensor.unsqueeze(0).cuda(0))
res = res.detach().cpu().numpy().reshape(-1)
fetures.append(res)
paths.append(img_item)
df = pd.DataFrame(fetures)
df["path"] = paths
save_path = f"{pic_dir}/frame.csv"
df.to_csv(save_path, index=False)
return f"提取特征完毕:\n {save_path}"
- 提取特征之后就需要对比当前图片和下一张图片的相似度,这里为了好评采用余弦距离作为标准!并且筛选相似度低于0.6的地方作为切割点!
def compute_similarly(path):
df = pd.read_csv(path)
try:
del df["Unnamed: 0"]
del df["index"]
except:
pass
df["rank"] = df.apply(lambda x: int(x["path"].split("/")[-1].split(".")[0]), axis=1)
df = df.sort_values("rank", ascending=True)
df_np = np.array(df.iloc[:, :-2])
diff_similary = []
cut_point = []
for index, row in tqdm(enumerate(df_np)):
feature_1 = row
num = index + 1
if num == len(df_np):
cut_point.append(index)
break
feature_2 = df_np[num]
similary = cos_similary(feature_1, feature_2)
if similary <=0.6:
cut_point.append(index)
diff_similary.append(similary)
#检测是否第一个为0
if cut_point[0] == 0:
pass
else:
cut_point.insert(0, 0)
print(cut_point)
save_video_dir = path.replace("frame.csv", "")
try:
num = merge_video(save_video_dir, cut_point)
return f"视频分割成功,分割了{num}条!\t 地址为:{save_video_dir}/video"
except Exception as e:
print(e)
return "任务失败请看后台!"
- 根据切割点,将图片组合成视频即可。
def merge_video(save_path, cut_points):
# cap = cv2.VideoCapture(video)
# fps = cap.get(cv2.CAP_PROP_FPS)
# width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
# height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
cut_points_tuple = []
for index, row in enumerate(cut_points):
if index+1 == len(cut_points):
break
cut_points_tuple.append([row+1, cut_points[index+1]])
total = 0
for f1, f2 in tqdm(cut_points_tuple):
if f2 - f1 < 30:
continue
video_path = f"{save_path}/video"
if not os.path.exists(video_path):
os.makedirs(video_path)
frame = cv2.imread(f"{save_path}/0/0.png")
frame_w, frame_h = frame.shape[1], frame.shape[0]
out = cv2.VideoWriter(f"{video_path}/video_{f1}_{f2}.mp4", fourcc, 30, (frame_w, frame_h))
for index in tqdm(range(f1, f2)):
count = index // 2000
frame = cv2.imread(f"{save_path}/{count}/{index}.png")
out.write(frame)
total += 1
out.release()
return total
代码说明
以上都是初版本的代码例子,为了方便更好的使用,临时用gradio
布置了一个界面下载,使用方法如下:
- 上传图片然后点击切割,结束后会给出地址。
- 将得到的地址,去掉最后那个数字(这里忘改了。。。),添加到下一栏:
- 将提取好的文件地址填入。
这就是我的方法,如果有好的希望可以更多的交流哈!