Module code >> skvideo.measure.videobliinds
Fork me on GitHub

Source code for skvideo.measure.videobliinds

from os.path import dirname
from os.path import join

import numpy as np
import scipy.fftpack
import scipy.io
import scipy.misc
import scipy.ndimage
import scipy.stats

from ..motion import blockMotion
from ..utils import *


def motion_feature_extraction(frames):
    # setup
    frames = frames.astype(np.float32)
    mblock=10
    h = gen_gauss_window(2, 0.5)
    # step 1: motion vector calculation
    motion_vectors = blockMotion(frames, method='N3SS', mbSize=mblock, p=np.int(1.5*mblock))
    motion_vectors = motion_vectors.astype(np.float32)

    # step 2: compute coherency
    Eigens = np.zeros((motion_vectors.shape[0], motion_vectors.shape[1], motion_vectors.shape[2], 2), dtype=np.float32)
    for i in range(motion_vectors.shape[0]):
      motion_frame = motion_vectors[i]

      upper_left = np.zeros_like(motion_frame[:, :, 0])
      lower_right= np.zeros_like(motion_frame[:, :, 0])
      off_diag = np.zeros_like(motion_frame[:, :, 0])
      scipy.ndimage.correlate1d(motion_frame[:, :, 0]**2, h, 0, upper_left, mode='reflect') 
      scipy.ndimage.correlate1d(upper_left, h, 1, upper_left, mode='reflect') 
      scipy.ndimage.correlate1d(motion_frame[:, :, 1]**2, h, 0, lower_right, mode='reflect') 
      scipy.ndimage.correlate1d(lower_right, h, 1, lower_right, mode='reflect') 
      scipy.ndimage.correlate1d(motion_frame[:, :, 1]*motion_frame[:, :, 0], h, 0, off_diag, mode='reflect') 
      scipy.ndimage.correlate1d(off_diag, h, 1, off_diag, mode='reflect')

      for y in range(motion_vectors.shape[1]):
        for x in range(motion_vectors.shape[2]):
          mat = np.array([
            [upper_left[y, x], off_diag[y, x]],
            [off_diag[y, x], lower_right[y, x]],
          ])
          w, _ = np.linalg.eig(mat)
          Eigens[i, y, x] = w 

    num = (Eigens[:, :, :, 0] - Eigens[:, :, :, 1])**2
    den = (Eigens[:, :, :, 0] + Eigens[:, :, :, 1])**2

    Coh10x10 = np.zeros_like(num)
    Coh10x10[den!=0] = num[den!=0] / den[den!=0]

    meanCoh10x10 = np.mean(Coh10x10)

    # step 3: global motion
    mode10x10 = np.zeros((motion_vectors.shape[0]), dtype=np.float32)
    mean10x10 = np.zeros((motion_vectors.shape[0]), dtype=np.float32)
    for i in range(motion_vectors.shape[0]):
      motion_frame = motion_vectors[i]
      motion_amplitude = np.sqrt(motion_vectors[i, :, :, 0]**2 + motion_vectors[i, :, :, 1]**2) 
      mode10x10[i] = scipy.stats.mode(motion_amplitude, axis=None)[0]
      mean10x10[i] = np.mean(motion_amplitude)

    motion_diff = np.abs(mode10x10 - mean10x10)
    G = np.mean(motion_diff) / (1 + np.mean(mode10x10))

    return np.array([meanCoh10x10, G])

def _extract_subband_feats(mscncoefs):
    # alpha_m,  = extract_ggd_features(mscncoefs)
    alpha_m, N, bl, br, lsq, rsq = aggd_features(mscncoefs.copy())
    pps1, pps2, pps3, pps4 = paired_product(mscncoefs)
    alpha1, N1, bl1, br1, lsq1, rsq1 = aggd_features(pps1)
    alpha2, N2, bl2, br2, lsq2, rsq2 = aggd_features(pps2)
    alpha3, N3, bl3, br3, lsq3, rsq3 = aggd_features(pps3)
    alpha4, N4, bl4, br4, lsq4, rsq4 = aggd_features(pps4)
    # print bl1, br1
    # print bl2, br2
    # print bl3, br3
    # print bl4, br4
    # exit(0)
    return np.array([alpha_m, (bl+br)/2.0]), np.array([
            alpha1, N1, bl1, br1,  # (V)
            alpha2, N2, bl2, br2,  # (H)
            alpha3, N3, bl3, br3,  # (D1)
            alpha4, N4, bl4, br4,  # (D2)
    ])

def extract_on_patches(img, blocksizerow, blocksizecol):
    h, w = img.shape
    blocksizerow = np.int(blocksizerow)
    blocksizecol = np.int(blocksizecol)
    patches = []
    for j in range(0, np.int(h-blocksizerow+1), np.int(blocksizerow)):
        for i in range(0, np.int(w-blocksizecol+1), np.int(blocksizecol)):
            patch = img[j:j+blocksizerow, i:i+blocksizecol]
            patches.append(patch)

    patches = np.array(patches)
    
    patch_features = []
    for p in patches:
        mscn_features, pp_features = _extract_subband_feats(p)
        patch_features.append(np.hstack((mscn_features, pp_features)))
    patch_features = np.array(patch_features)

    return patch_features

def computequality(img, blocksizerow, blocksizecol, mu_prisparam, cov_prisparam):
    img = img[:, :, 0]
    h, w = img.shape

    if (h < blocksizerow) or (w < blocksizecol):
        print("Input frame is too small")
        exit(0)

    # ensure that the patch divides evenly into img
    hoffset = (h % blocksizerow)
    woffset = (w % blocksizecol)

    if hoffset > 0: 
        img = img[:-hoffset, :]
    if woffset > 0:
        img = img[:, :-woffset]

    img = img.astype(np.float32)
    img2 = scipy.misc.imresize(img, 0.5, interp='bicubic', mode='F')

    mscn1, var, mu = compute_image_mscn_transform(img, extend_mode='nearest')
    mscn1 = mscn1.astype(np.float32)

    mscn2, _, _ = compute_image_mscn_transform(img2, extend_mode='nearest')
    mscn2 = mscn2.astype(np.float32)

    feats_lvl1 = extract_on_patches(mscn1, blocksizerow, blocksizecol)
    feats_lvl2 = extract_on_patches(mscn2, blocksizerow/2, blocksizecol/2)

    # stack the scale features
    feats = np.hstack((feats_lvl1, feats_lvl2))# feats_lvl3))

    mu_distparam = np.mean(feats, axis=0)
    cov_distparam = np.cov(feats.T)

    invcov_param = np.linalg.pinv((cov_prisparam + cov_distparam)/2)

    xd = mu_prisparam - mu_distparam 
    quality = np.sqrt(np.dot(np.dot(xd, invcov_param), xd.T))[0][0]

    return np.hstack((mu_distparam, [quality]))


def compute_niqe_features(frames):
    blocksizerow = 96
    blocksizecol = 96

    T, M, N, C = frames.shape
    assert ((M >= blocksizerow*2) & (N >= blocksizecol*2)), "Video too small for NIQE extraction"

    module_path = dirname(__file__)
    params = scipy.io.loadmat(join(module_path, 'data', 'frames_modelparameters.mat'))
    mu_prisparam = params['mu_prisparam']
    cov_prisparam = params['cov_prisparam']

    niqe_features = np.zeros((frames.shape[0]-10, 37))
    idx = 0
    for i in range(5, frames.shape[0]-5):
      niqe_features[idx] = computequality(frames[i], blocksizerow, blocksizecol, mu_prisparam, cov_prisparam)
      idx += 1

    niqe_features = np.mean(niqe_features, axis=0)
    return niqe_features

def temporal_dc_variation_feature_extraction(frames):
    frames = frames.astype(np.float32)
    mblock=16
    mbsize=16
    ih = np.int(frames.shape[1]/mbsize)*mbsize
    iw = np.int(frames.shape[2]/mbsize)*mbsize
    # step 1: motion vector calculation
    motion_vectors = blockMotion(frames, method='N3SS', mbSize=mblock, p=7)
    # step 2: compensated temporal dct differences
    dct_motion_comp_diff = np.zeros((motion_vectors.shape[0], motion_vectors.shape[1], motion_vectors.shape[2]), dtype=np.float32)
    for i in range(motion_vectors.shape[0]):
      for y in range(motion_vectors.shape[1]):
        for x in range(motion_vectors.shape[2]):
          patchP = frames[i+1, y*mblock:(y+1)*mblock, x*mblock:(x+1)*mblock, 0].astype(np.float32)
          patchI = frames[i, y*mblock+motion_vectors[i, y, x, 0]:(y+1)*mblock+motion_vectors[i, y, x, 0], x*mblock+motion_vectors[i, y, x, 1]:(x+1)*mblock+motion_vectors[i, y, x, 1], 0].astype(np.float32)
          diff = patchP - patchI
          t = scipy.fftpack.dct(scipy.fftpack.dct(diff, axis=1, norm='ortho'), axis=0, norm='ortho')
          #dct_motion_comp_diff[i, y*mblock:(y+1)*mblock, x*mblock:(x+1)*mblock] = t 
          dct_motion_comp_diff[i, y, x] = t[0, 0] 

    dct_motion_comp_diff = dct_motion_comp_diff.reshape(motion_vectors.shape[0], -1) 

    std_dc = np.std(dct_motion_comp_diff, axis=1)
    dt_dc_temp = np.abs(std_dc[1:] - std_dc[:-1])

    dt_dc_measure1 = np.mean(dt_dc_temp)
    return np.array([dt_dc_measure1])

def NSS_spectral_ratios_feature_extraction(frames):
    def zigzag(data):
      nrows, ncols = data.shape
      d=sum([list(data[::-1,:].diagonal(i)[::(i+nrows+1)%2*-2+1])for i in range(-nrows,nrows+len(data[0]))], [])
      return np.array(d)

    mblock=5

    # step 1: compute local dct frame differences
    dct_diff5x5 = np.zeros((frames.shape[0]-1, np.int(frames.shape[1]/mblock), np.int(frames.shape[2]/mblock),mblock**2), dtype=np.float32)
    for i in range(dct_diff5x5.shape[0]):
      for y in range(dct_diff5x5.shape[1]):
        for x in range(dct_diff5x5.shape[2]):
          diff = frames[i+1, y*mblock:(y+1)*mblock, x*mblock:(x+1)*mblock].astype(np.float32) - frames[i, y*mblock:(y+1)*mblock, x*mblock:(x+1)*mblock].astype(np.float32)  
          t = scipy.fftpack.dct(scipy.fftpack.dct(diff, axis=1, norm='ortho'), axis=0, norm='ortho')
          dct_diff5x5[i, y, x] = t.ravel()
    dct_diff5x5 = dct_diff5x5.reshape(dct_diff5x5.shape[0],dct_diff5x5.shape[1] * dct_diff5x5.shape[2], -1)

    # step 2: compute gamma
    g = np.arange(0.03, 10+0.001, 0.001)
    r = (scipy.special.gamma(1/g) * scipy.special.gamma(3/g)) / (scipy.special.gamma(2/g)**2)

    gamma_matrix = np.zeros((dct_diff5x5.shape[0], mblock**2), dtype=np.float32) 
    for i in range(dct_diff5x5.shape[0]):
      for s in range(mblock**2):
        temp = dct_diff5x5[i, :, s]
        mean_gauss = np.mean(temp)
        var_gauss = np.var(temp, ddof=1)
        mean_abs = np.mean(np.abs(temp - mean_gauss))**2
        rho = var_gauss/(mean_abs + 1e-7)

        gamma_gauss = 11
        for x in range(len(g)-1):
          if (rho <= r[x]) and (rho > r[x+1]):
            gamma_gauss = g[x]
            break
        gamma_matrix[i, s] = gamma_gauss

    gamma_matrix = gamma_matrix.reshape(dct_diff5x5.shape[0], mblock, mblock)

    #zigzag = lambda N,w,h:[N[i*w+s-i]for s in range(w+h+1)for i in range(h)[::s%2*2-1]if-1<s-i<w]

    freq_bands = np.zeros((dct_diff5x5.shape[0], mblock**2))
    for i in range(dct_diff5x5.shape[0]):
      freq_bands[i] = zigzag(gamma_matrix[i]) 

    lf_gamma5x5 = freq_bands[:, 1:np.int((mblock**2-1)/3)+1]
    mf_gamma5x5 = freq_bands[:, np.int((mblock**2-1)/3)+1:2*np.int((mblock**2-1)/3)+1]
    hf_gamma5x5 = freq_bands[:, np.int(2*(mblock**2-1)/3)+1:]

    geomean_lf_gam = scipy.stats.mstats.gmean(lf_gamma5x5.T)
    geomean_mf_gam = scipy.stats.mstats.gmean(mf_gamma5x5.T)
    geomean_hf_gam = scipy.stats.mstats.gmean(hf_gamma5x5.T)

    geo_high_ratio = scipy.stats.mstats.gmean(geomean_hf_gam/(0.1 + (geomean_mf_gam + geomean_lf_gam)/2))
    geo_low_ratio = scipy.stats.mstats.gmean(geomean_mf_gam/(0.1 + geomean_lf_gam))
    geo_HL_ratio = scipy.stats.mstats.gmean(geomean_hf_gam/(0.1 + geomean_lf_gam))
    geo_HM_ratio = scipy.stats.mstats.gmean(geomean_hf_gam/(0.1 + geomean_mf_gam))
    geo_hh_ratio = scipy.stats.mstats.gmean(((geomean_hf_gam + geomean_mf_gam)/2)/(0.1 + geomean_lf_gam))

    mean_dc = np.mean(dct_diff5x5[:, :, 0], axis=1) 
    dt_dc_measure2 = np.mean(np.abs(mean_dc[1:] - mean_dc[:-1]))
    
    return np.array([dt_dc_measure2, geo_HL_ratio, geo_HM_ratio, geo_hh_ratio, geo_high_ratio, geo_low_ratio])

[docs]def videobliinds_features(videoData): """Computes Video Bliinds features. [#f1]_ Since this is a referenceless quality algorithm, only 1 video is needed. This function provides the raw features used by the algorithm. Parameters ---------- videoData : ndarray Reference video, ndarray of dimension (T, M, N, C), (T, M, N), (M, N, C), or (M, N), where T is the number of frames, M is the height, N is width, and C is number of channels. Returns ------- features : ndarray, shape (46,) | The individual features of the algorithm. The features are arranged as follows: | | features[:36] : spatial niqe vector averaged over the video, shape (36,) | features[36] : niqe naturalness score, shape (1,) | features[37:39] : DC measurements between frames, shape (2,) | features[39:44] : Natural Video Statistics, shape (5,) | features[44] : Motion coherence, shape (1,) | features[45] : Global motion, shape (1,) References ---------- .. [#f1] M. Saad and A.C. Bovik, "Blind prediction of natural video quality" IEEE Transactions on Image Processing, December 2013. """ videoData = vshape(videoData) T, M, N, C = videoData.shape assert C == 1, "videobliinds called with video having %d channels. Please supply only the luminance channel." % (C,) dt_dc_measure1 = temporal_dc_variation_feature_extraction(videoData) spectral_features = NSS_spectral_ratios_feature_extraction(videoData) temporal_features = motion_feature_extraction(videoData) niqe_features = compute_niqe_features(videoData) features = np.hstack(( niqe_features, np.log(1+dt_dc_measure1), np.log(1+spectral_features), np.log(1+temporal_features), )) return features