• 来源

https://blog.51cto.com/u_16099242/10447591

  • 改进

愿视频和处理后的视频中心画圈;原视频与处理后的合并;加了耗时计数。

后期计划改进:缓冲满了就清除是错误的,应该采用挤出法。

  • 具体代码
import numpy as np
import cv2
import time



# 给画面画网格线
def video_grid(frame, width, height, direction, color):
    if (not merge_video):
        return

    if (direction):
        for i in range(1, int(width/grid_size)+1):
            cv2.line(frame,
                     (int(grid_size*i), int(0)          ),
                     (int(grid_size*i), int(height     )),
                     color, 2)
    else:
        for i in range(1, int(height/grid_size)+1):
            cv2.line(frame,
                     (int(0          ), int(grid_size*i)),
                     (int(width      ), int(grid_size*i)),
                     color, 2)

def video_circle(frame, width, height, color):
    cv2.circle(frame,
               (int(width/2), int(height/2)),
               5, color, 3)

    cv2.circle(frame,
                   (int(width/2), int(height/2)),
                   grid_size, color, 2)
                   
    size = min(width, height)
    for i in range(1, int(size/grid_size)+1):
        cv2.circle(frame,
                   (int(width/2), int(height/2)),
                   grid_size, color, 2)
                   
# 标出匹配的关键点
def video_keypoints(frame, matches, keypoints, check_x, check_y):
    for match in matches:
        curr_point = keypoints[match.trainIdx].pt
        cv2.circle(frame,
                   (int(curr_point[0]+check_x), int(curr_point[1]+check_y)),
                   3, (0, 255, 0), 2)

# 文件名
def get_filename():
    filename = 'stab'
    if (merge_video):
        if (merge_horizontal):
            filename += '_h'
        else:
            filename += '_v'

    filename += '.mp4'
    return filename

# 计算剪裁位置,避免越界
def get_dst_pos(crop_p, mean_motion_p):
    dst_p = int(crop_p + mean_motion_p)
    if (dst_p < 0):
        dst_p = 0
    elif (dst_p > crop_p*2):
        dst_p = crop_p*2

    return dst_p


def movingAverage(curve, radius):
    window_size = 2 * radius + 1
    # 定义过滤器
    f = np.ones(window_size) / window_size
    # 为边界添加填充
    curve_pad = np.pad(curve, (radius, radius), 'edge')
    # 应用卷积
    curve_smoothed = np.convolve(curve_pad, f, mode='same')
    # 删除填充
    curve_smoothed = curve_smoothed[radius:-radius]
    # 返回平滑曲线
    return curve_smoothed


def smooth(trajectory):
    smoothed_trajectory = np.copy(trajectory)
    # 过滤x, y和角度曲线
    for i in range(3):
        smoothed_trajectory[:, i] = movingAverage(
            trajectory[:, i], radius=SMOOTHING_RADIUS)

    return smoothed_trajectory


def fixBorder(frame):
    s = frame.shape
    T = cv2.getRotationMatrix2D((s[1] / 2, s[0] / 2), 0, ZOOM_RATIO)
    frame = cv2.warpAffine(frame, T, (s[1], s[0]))
    return frame

# 在不移动中心的情况下,将图像缩放
ZOOM_RATIO=1.1

FRAME_BUFFER_COUNT=30

# 尺寸越大,视频越稳定,但对突然平移的反应越小
SMOOTHING_RADIUS = 50

# 是否合并视频
merge_video      = True
# 合并视频时,水平或垂直
merge_horizontal = True

# 网格线间隔
grid_size        = 100


# 读取输入视频
cap = cv2.VideoCapture('../test-1920X1080.mp4')

# 获取视频流的宽度和高度
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 获取每秒帧数(fps)
fps = cap.get(cv2.CAP_PROP_FPS)

src_width = w
src_height= h
# 只检测中心位置的区域。
check_n = 1/5
check_w = int( src_width *check_n)
check_h = int( src_height*check_n)
check_x = int((src_width -check_w)/2)
check_y = int((src_height-check_h)/2)

# 剪裁范围
crop_n  = 1/5
crop_x  = int(src_width *   crop_n/2)
crop_y  = int(src_height*   crop_n/2)
crop_w  = int(src_width *(1-crop_n) )
crop_h  = int(src_height*(1-crop_n) )

write_w = w
write_h = h
if (merge_video):
    if (merge_horizontal):
        write_w += w
    else:
        write_h += h
        
# 设置视频输出格式
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out    = cv2.VideoWriter(get_filename(), fourcc, cap.get(cv2.CAP_PROP_FPS), (write_w, write_h))

# # 得到帧数
# n_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

# 预定义转换numpy矩阵
# transforms = np.zeros((n_frames - 1, 3), np.float32)

prev_gray = []

k = 0

# 准备存储
transforms = np.zeros((FRAME_BUFFER_COUNT, 3), np.float32)

work_cost = 0
frame_counter = 0

while cap.isOpened():

    # print(k)

    # 读取一帧
    success, curr = cap.read()

    # 耗时开始
    frame_counter += 1
    current_time = time.time()
    
    # 是否还有下一帧,关闭
    if not success:
        break
 
    video_circle(curr, w, h, (0, 0, 255))
    # 转换为灰度图
    curr_gray = cv2.cvtColor(curr, cv2.COLOR_BGR2GRAY)

    # 为了计算帧差,要把前几帧放入列表中
    prev_gray.append(curr_gray)

    if len(prev_gray) >= 2:
        # 检测前一帧的特征点
        prev_pts = cv2.goodFeaturesToTrack(prev_gray[k - 1],
                                           maxCorners=200,
                                           qualityLevel=0.01,
                                           minDistance=30,
                                           blockSize=3)
        # 计算光流(即轨迹特征点) 前一张 当前张 前一张特征
        curr_pts, status, err = cv2.calcOpticalFlowPyrLK(prev_gray[k - 1], curr_gray, prev_pts, None)

        # 检查完整性
        assert prev_pts.shape == curr_pts.shape

        # 只过滤有效点
        idx = np.where(status == 1)[0]
        prev_pts = prev_pts[idx]
        curr_pts = curr_pts[idx]

        # 找到变换矩阵
        m, inlier = cv2.estimateAffine2D(prev_pts, curr_pts)

        # 提取traslation
        dx = m[0, 2]
        dy = m[1, 2]

        # 提取旋转角
        da = np.arctan2(m[1, 0], m[0, 0])

        # 存储转换
        transforms[k] = [dx, dy, da]

        # cv2.imshow("B", curr_gray)
        # cv2.waitKey(1)
    k += 1
    # 避免内存泄露,清空列表,重新计算
    if k >= FRAME_BUFFER_COUNT:
        prev_gray = []
        k = 0

    if len(prev_gray) >= 3:
        # 使用累积变换和计算轨迹
        trajectory = np.cumsum(transforms, axis=0)

        # 创建变量来存储平滑的轨迹
        smoothed_trajectory = smooth(trajectory)

        # 计算smoothed_trajectory与trajectory的差值
        difference = smoothed_trajectory - trajectory

        # 计算更新的转换数组
        transforms_smooth = transforms + difference

        # 从新的转换数组中提取转换
        dx = transforms_smooth[k, 0]
        dy = transforms_smooth[k, 1]
        da = transforms_smooth[k, 2]

        # 根据新的值重构变换矩阵
        m = np.zeros((2, 3), np.float32)
        m[0, 0] = np.cos(da)
        m[0, 1] = -np.sin(da)
        m[1, 0] = np.sin(da)
        m[1, 1] = np.cos(da)
        m[0, 2] = dx
        m[1, 2] = dy

        # 应用仿射包装到给定的框架
        frame_stabilized = cv2.warpAffine(curr, m, (w, h))

        # Fix border artifacts
        frame_stabilized = fixBorder(frame_stabilized)
        video_circle(frame_stabilized, w, h, (0, 255, 255))
        # cv2.imshow("B", frame_stabilized)
        # cv2.waitKey(1)
        
        if (merge_horizontal):
            out_frame = cv2.hconcat([curr, frame_stabilized])
        else:
            out_frame = cv2.vconcat([curr, frame_stabilized])
        
        work_cost += (time.time()-current_time)
        
        # video_grid(out_frame, write_w, write_h, (not merge_horizontal), (0, 0, 255))
        out.write(out_frame)
        
print('cost per frame(ms)=', (work_cost/frame_counter*1000))
# 释放视频读取和写入对象
cap.release()
out.release()
# 关闭所有OpenCV窗口
cv2.destroyAllWindows()

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐