用Python实现将滑动屏幕保存的录屏视频自动拼接为长截图

目录

  • 一、需求背景
  • 二、录屏视频
  • 三、设计思路
    • 3.4 计算重合度
    • 3.3 计算偏移量
    • 3.5 模板匹配
    • 3.1 读取图像
    • 3.2 计算不变区域
    • 3.6 图像拼接
      • 3.6.1 底部拼接
      • 3.6.2 平均拼接
  • 四、实现代码
  • 五、最终结果

一、需求背景

如果你有一台苹果手机,想要截取超过一个屏幕高度的长截图,需要多次截屏之后,再使用另外一个APP,才能将多张图像拼接成一整张长截图

「Picsew」长截屏软件:1
用Python实现将滑动屏幕保存的录屏视频自动拼接为长截图_第1张图片

如果你用的是安卓手机,可以使用系统自带的长截图功能。

但是有的APP不支持长截屏怎么办?有的画面比长截图能截取的范围还要长怎么办?长截图滚动的速度太慢了,还有更快的办法吗?

我就遇到了这样的场景,我希望获取微信步数排行榜单里的数据,但是没有找到抓包的接口,所以只能先截屏、然后再做图像识别

但是微信步数排行的榜单实在太长了,截取完成一次要截好几屏,并且长截屏的速度真的太慢了。

于是我想到一个方法,先录制屏幕滚动的视频,然后再拼图长截屏,会不会更快一点呢?

二、录屏视频

需要注意以最高的画质和帧率屏幕录像,录屏画质越高,之后拼图的画质自然就越高。

并且录屏滚动的速度可以慢一点,避免两帧之间的画面移动的距离太远,为后面的拼图带来困难。

三、设计思路

计算每两帧图像之间的偏移量,是能够合成长截图的基础。而能够找到两张图像的偏移量,首先要找到一种方法评估两张图像的重合度

所以3.4节虽然是程序运行的第4个步骤,但却我首先尝试解决的问题,我首先介绍这个环节:

3.4 计算重合度

将视频中的每一帧图像读取为矩阵,将两帧图像的矩阵偏移后相减,如果得到的值全部为,那么这个偏移量就是这两帧图像偏移的距离

但是事与愿违,视频中的画质是有损失的。图像重叠区域的像素并不精确相等,只是近似一致。所以需要找到一种评估两个区域像素相似程度的指标。

将重叠区域的像素数值相减,然后再取绝对值的平均值,作为评估相似程度的依据,数值越小则说明相似程度越高

def overlay(d1, d2, d):
    '''计算重叠部分差异绝对值的平均值'''
    L1 = d1.shape[0]
    L2 = d2.shape[0]
    dd1 = d1[max(0,  d):min(L1, L2+d)]
    dd2 = d2[max(-d, 0):min(L1-d, L2)]
    return np.abs(- 1 * dd2 + dd1).mean() # safe plus

3.3 计算偏移量

将两张图像从上到下滑动匹配一遍,找到重合度最高、也就是差值绝对值的平均值最小的那一次,认为是两帧图像的相对偏移量:

def offset(img1, img2):
    '''计算两张图片的平移偏移量'''
    height = img1.shape[0]
    avg = np.inf
    for h in range(- 100, height // 2): # 搜寻范围
        avg1 = overlay(img1, img2, h)
        if avg > avg1:
            avg = avg1
            dh = h
    return dh, avg

这个方法得到了不错的运行结果,但是我很快就发现了问题——运行效率太低

计算两帧图像的数据需要好几秒钟,视频一秒就可以有60帧,十几秒就可能有上千帧。总时间太长了,还有别的更好的办法吗?

3.5 模板匹配

最近在学习图像识别算法,了解到有一个叫做模板匹配的算法2。Python的图形处理cv2库内建这种算法,参数method有3种可选的匹配模式,本例中设置为了cv2.TM_SQDIFF_NORMED,其他模式经测试运行速度和效果也基本一致。

计算运行720×1306分辨率的图像,速度大约可以达到每秒计算15帧

def match(image, templ, method):
    '''模板匹配图像位置'''
    th, tw = templ.shape[:2]
    result = cv2.matchTemplate(image, templ, method)
    result2 = (result - result.min()) / (result.max() - result.min())
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
    return min_loc if method == cv2.TM_SQDIFF_NORMED else max_loc

3.1 读取图像

使用迭代器逐帧读取视频中的图像数据:

def imiter(file, st=None, ed=None):
    '''封装的视频读取迭代器'''
    cap = cv2.VideoCapture(file)
    while cap.isOpened():
        ret, img = cap.read()
        if not ret:
            cap.release()
            return
        yield img[st:ed]

3.2 计算不变区域

屏幕录像中,顶部状态栏和底部导航栏有时会占用一部分高度,但这不是我们需要拼接的内容。我在函数中设置了sted参数,可以手动设定需要裁切的高度

也可以自动测量,逐一读取每行的像素数据,计算每行的方差,找到数值突变的那个部分,就是需要裁切的起始结束位置。

def repeat(file):
    '''计算视频中存在变化的区域起止范围'''
    data = []
    for img in imiter(file):
        h, w, n = img.shape
        data.append(img.reshape((h, -1)).mean(1))
    data = np.array(data)
    var = data.var(0)
    where = np.where(var > var.mean())[0]
    return where.min(), where.max() + 1

3.6 图像拼接

综合前面的方法,就可以计算出录屏视频中各帧图像的偏移数值序列,将图像拼接成完整的长截图。

图像拼接有两种策略:

3.6.1 底部拼接

第一种方法,将新一帧的追加内容直接拼接在图像的底部

def join_bottom(file, gaps, st=None, ed=None):
    '''滚动拼接到图片底部'''
    imgs = imiter(file, st, ed)
    data = next(imgs)
    for n, (img, gap) in enumerate(zip(imgs, gaps)):
        data = np.concatenate([data, img[img.shape[0] - gap:]])
    return data

3.6.2 平均拼接

第二种方法,将重叠区域的图像平均后再保存。对于屏幕录屏来说,每一帧的画质是有损失的,进行平均处理后,画质会有明显的提升:

def join_avg(file, gaps, st=None, ed=None):
    '''加权拼贴图像'''
    imgs = imiter(file, st, ed)
    data = next(imgs).astype(np.int16)
    data = np.concatenate([data, np.zeros((sum(gaps), *data.shape[1:]), np.int16)]) # 扩展填0
    weight = np.zeros_like(data)
    height = data.shape[0]
    st = 0
    for n, (img, gap) in enumerate(zip(imgs, gaps)):
        st += gap
        ed = min(height, st + img.shape[0])
        data  [st:ed] += img[:ed-st]
        weight[st:ed] += np.ones_like(img[:ed-st])
    return data // weight

不过这种方法,一旦某两帧的偏移值计算出现错误,拼接就会错位,导致保存图像出现重影,并且不容易被看出来。

四、实现代码

把之前的内容串起来,就能得到录屏视频生成长截图的程序,可以留给读者作为课后练习。

我也把完整的程序、和测试用例上传到了网盘,有需要的同学可以自行下载运行:

https://download.csdn.net/download/weixin_39804265/84993982

五、最终结果

测试了一段12秒的录屏视频,总共908帧,合成了720×50557分辨率的长截图,相当于39个屏幕高度,总共用时62.98秒,平均每秒处理14.4帧图像

而之前用的滑动对齐法,同样的908帧的视频,总共用时,呃。。7853秒,也就是2小时11分钟。。耗时为模板匹配方法的124.7倍,平均每帧用时8.66秒

使用手机录制示例:

程序拼接效果示例:

用Python实现将滑动屏幕保存的录屏视频自动拼接为长截图_第2张图片


  1. Picsew——iPhone上的截图工具: https://baijiahao.baidu.com/s?id=1660058225659119872 ↩︎

  2. python实现模板匹配: https://www.cnblogs.com/april0315/p/13741888.html ↩︎

你可能感兴趣的:(Python,python)