1

Я пытаюсь реализовать алгоритм оптического потока Хорн-Шунка по NumPy и OpenCV я использую Horn-Schunck method on wiki и original paperХорн-Шунка проблема реализации оптического потока

Но моя реализация не будет работать на следующем простом примере

Frame1:

[[ 0 0 0 0 0 0 0 0 0 0] 
[ 0 255 255 0 0 0 0 0 0 0] 
[ 0 255 255 0 0 0 0 0 0 0] 
[ 0 0 0 0 0 0 0 0 0 0] 
[ 0 0 0 0 0 0 0 0 0 0]] 

frame2:

[[ 0 0 0 0 0 0 0 0 0 0] 
[ 0 0 0 255 255 0 0 0 0 0] 
[ 0 0 0 255 255 0 0 0 0 0] 
[ 0 0 0 0 0 0 0 0 0 0] 
[ 0 0 0 0 0 0 0 0 0 0]] 

Это всего лишь маленький белый прямоугольник, который перемещается на 2 пикселя на frame2. . Моя реализация создает следующий поток. U часть потока (применяю np.round к каждой части потока. Оригинальные значения довольно такие же):

[[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.] 
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.] 
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.] 
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.] 
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]] 

В части потока:

[[ 0. 1. 0. -1. -0. 0. 0. 0. 0. 0.] 
[-0. -0. 0. 0. 0. 0. 0. 0. 0. 0.] 
[-0. -1. -0. 1. 0. 0. 0. 0. 0. 0.] 
[-0. -0. -0. 0. 0. 0. 0. 0. 0. 0.] 
[-0. -0. -0. 0. 0. 0. 0. 0. 0. 0.]] 

Это выглядит как этот поток неверно (Потому что, если я переместить каждый пиксель frame2 в направлении соответствующего компонента потока я никогда не frame1) Кроме того, моя реализация не будет работать на реальных изображениях

Но если я переместить прямоугольник на 1 пиксель вправо (или влево или сверху или вниз) моя продукция реализация: U часть потока:

[[1 1 1 .....] 
[1 1 1 .....] 
...... 
[1 1 1 .....]] 

В части потока:

[[0 0 0 .....] 
[0 0 0 .....] 
...... 
[0 0 0 .....]] 

Я полагаю, что этот поток является правильным, потому что я могу реконструировать раму 1 с помощью следующей процедуры

def translateBrute(img, u, v): 
    res = np.zeros_like(img) 
    u = np.round(u).astype(np.int) 
    v = np.round(v).astype(np.int) 
    for i in xrange(img.shape[0]): 
     for j in xrange(img.shape[1]): 
      res[i, j] = takePixel(img, i + v[i, j], j + u[i, j]) 
    return res 

, где takePixel просто функция, которая возвращает интенсивность пикселя если входные координаты лежат внутри изображения или интенсивности на границе изображения в противном случае

Это моя реализация Тион

import cv2 
import sys 
import numpy as np 

def takePixel(img, i, j): 
    i = i if i >= 0 else 0 
    j = j if j >= 0 else 0  
    i = i if i < img.shape[0] else img.shape[0] - 1 
    j = j if j < img.shape[1] else img.shape[1] - 1 
    return img[i, j] 

#Numerical derivatives from original paper: http://people.csail.mit.edu/bkph/papers/Optical_Flow_OPT.pdf 
def xDer(img1, img2): 
    res = np.zeros_like(img1) 
    for i in xrange(res.shape[0]): 
     for j in xrange(res.shape[1]): 
      sm = 0 
      sm += takePixel(img1, i,  j + 1) - takePixel(img1, i,  j) 
      sm += takePixel(img1, i + 1, j + 1) - takePixel(img1, i + 1, j) 
      sm += takePixel(img2, i,  j + 1) - takePixel(img2, i,  j) 
      sm += takePixel(img2, i + 1, j + 1) - takePixel(img2, i + 1, j) 
      sm /= 4.0 
      res[i, j] = sm 
    return res 

def yDer(img1, img2): 
    res = np.zeros_like(img1) 
    for i in xrange(res.shape[0]): 
     for j in xrange(res.shape[1]): 
      sm = 0 
      sm += takePixel(img1, i + 1, j ) - takePixel(img1, i, j ) 
      sm += takePixel(img1, i + 1, j + 1) - takePixel(img1, i, j + 1) 
      sm += takePixel(img2, i + 1, j ) - takePixel(img2, i, j ) 
      sm += takePixel(img2, i + 1, j + 1) - takePixel(img2, i, j + 1) 
      sm /= 4.0 
      res[i, j] = sm 
    return res 

def tDer(img, img2): 
    res = np.zeros_like(img) 
    for i in xrange(res.shape[0]): 
     for j in xrange(res.shape[1]): 
      sm = 0 
      for ii in xrange(i, i + 2): 
       for jj in xrange(j, j + 2): 
        sm += takePixel(img2, ii, jj) - takePixel(img, ii, jj) 
      sm /= 4.0 
      res[i, j] = sm 
    return res 

averageKernel = np.array([[ 0.08333333, 0.16666667, 0.08333333], 
          [ 0.16666667, 0.  , 0.16666667], 
          [ 0.08333333, 0.16666667, 0.08333333]], dtype=np.float32) 
#average intensity around flow in point i,j. I use filter2D to improve performance. 
def average(img): 
    return cv2.filter2D(img.astype(np.float32), -1, averageKernel) 

def translateBrute(img, u, v): 
    res = np.zeros_like(img) 
    u = np.round(u).astype(np.int) 
    v = np.round(v).astype(np.int) 
    for i in xrange(img.shape[0]): 
     for j in xrange(img.shape[1]): 
      res[i, j] = takePixel(img, i + v[i, j], j + u[i, j]) 
    return res 

#Core of algorithm. Iterative scheme from wiki: https://en.wikipedia.org/wiki/Horn%E2%80%93Schunck_method#Mathematical_details 
def hornShunckFlow(img1, img2, alpha): 
    img1 = img1.astype(np.float32) 
    img2 = img2.astype(np.float32) 

    Idx = xDer(img1, img2) 
    Idy = yDer(img1, img2) 
    Idt = tDer(img1, img2) 

    u = np.zeros_like(img1) 
    v = np.zeros_like(img1) 

    #100 iterations enough for small example 
    for iteration in xrange(100): 
     u0 = np.copy(u) 
     v0 = np.copy(v) 

     uAvg = average(u0) 
     vAvg = average(v0) 
     # '*', '+', '/' operations in numpy works component-wise 
     u = uAvg - 1.0/(alpha**2 + Idx**2 + Idy**2) * Idx * (Idx * uAvg + Idy * vAvg + Idt) 
     v = vAvg - 1.0/(alpha**2 + Idx**2 + Idy**2) * Idy * (Idx * uAvg + Idy * vAvg + Idt) 
     if iteration % 10 == 0: 
      print 'iteration', iteration, np.linalg.norm(u - u0) + np.linalg.norm(v - v0) 

    return u, v 

if __name__ == '__main__': 
    img1c = cv2.imread(sys.argv[1]) 
    img2c = cv2.imread(sys.argv[2]) 
    img1g = cv2.cvtColor(img1c, cv2.COLOR_BGR2GRAY) 
    img2g = cv2.cvtColor(img2c, cv2.COLOR_BGR2GRAY) 

    u, v = hornShunckFlow(img1g, img2g, 0.1) 
    imgRes = translateBrute(img2g, u, v) 
    cv2.imwrite('res.png', imgRes) 
    print img1g 
    print translateBrute(img2g, u, v) 

схема оптимизации берутся из wikipedia и численные производные взяты из оригинальной статьи.

У кого-нибудь есть идея, почему моя реализация порождает неправильный поток? я могу предоставить любую дополнительную информацию, если это необходимо

PS Извините за мой бедный английский

UPD: я реализовать функцию

def grad(img): 
    Idx = cv2.filter2D(img, -1, np.array([ 
      [-1, -2, -1], 
      [ 0, 0, 0], 
      [ 1, 2, 1]], dtype=np.float32)) 
    Idy = cv2.filter2D(img, -1, np.array([ 
      [-1, 0, 1], 
      [-2, 0, 2], 
      [-1, 0, 1]], dtype=np.float32)) 
    return Idx, Idy 

def hornShunckCost(Idx, Idy, Idt, u, v, alpha): 
    #return sum(sum(It**2)) 
    udx, udy = grad(u) 
    vdx, vdy = grad(v) 
    return (sum(sum((Idx*u + Idy*v + Idt)**2)) + 
      (alpha**2)*(sum(sum(udx**2)) + 
         sum(sum(udy**2)) + 
         sum(sum(vdx**2)) + 
         sum(sum(vdy**2)) 
       )) 

Хорн-Шунка стоимость и проверить значение этой функции внутри итераций

if iteration % 10 == 0: 
      print 'iter', iteration, np.linalg.norm(u - u0) + np.linalg.norm(v - v0) 
      print hornShunckCost(Idx, Idy, Idt, u, v, alpha) 

Если я использую простой пример с прямоугольником, который был перемещен одним пикселем, все, что я s ok: значение функции снижения стоимости на каждом шаге. Но на примере с прямоугольником, который был сдвинут на два пикселя, значение функции стоимости увеличивается на каждом шагу. Такое поведение алгоритма действительно странно Возможно, я выбрал неправильный способ вычисления функции затрат.

ответ

0

Я потерял тот факт, что классическая схема Хорна-Шунка использует линеаризованный термин данных (I1 (x, y) - I2 (x + u (x, y), y + v (x, y))). Эта линеаризация облегчает оптимизацию, но запрещает большие перемещения

Для обработки больших перемещений есть следующий подход Pyramidal Horn-Schunck

Смежные вопросы