2016年6月1日 星期三

神魔之塔的演算法研究...


Ref : http://35around.blogspot.tw/2014/07/ui-test-script-for-android-puzzle-and_14.html


接下來是最重要的演算法的部分, 如何設計一個好的演算法來求得傷害最高的路徑, 說真的, 我覺得要想到一個很好的演算法頗困難的, 還好目前的電腦cpu都還蠻強的, 加上其實盤面並不會很大, 只有6*5的大小, 所以在這邊我直接使用BFS方式來求解。實作的方式為: 針對盤面上的每一個珠子, 計算它可以往外前進的八個方向, 每走一步之後就計算目前盤面可以消除區域, 並計算落下之後是否造成下次的消除, 再代入上述的公式, 並且設定一個珠子最多可以走幾步, 最後就我們就可以得到每一個起始珠子走過的路徑的Combo數及攻擊力的權重表格, 透過這個表格我們就可以取到一個攻擊力最高的路徑了, 下面為pseudocode:
for row=1, 5 
    for col=1, 6
        solutions.add(new Solution(cursor(row, col));
while not solutions.isEmpty()
    solution = solutions.pop()
    if solution.depth >= MAX_DEPTH
        continue
    for move=1, 8
        newSolution = moveOrbs(solution, move)
        calculateWeight(newSolution) 
        solutions.add(newSolution) 


這個就是BFS 的演算法.......   這邊有提到是  Ref

https://github.com/kennytm/pndopt

接下來就是  code 的 study



row ->  y
col  ->  x


function evolve_solutions(solutions, weights, dir_step) {
    var new_solutions = [];
    solutions.forEach(function(s) {
        if (s.is_done) {
            return;
        }
        for (var dir = 0; dir < 8; dir += dir_step) {
            if (!can_move_orb_in_solution(s, dir)) {
                continue;
            }
            var solution = copy_solution(s);
            in_place_swap_orb_in_solution(solution, dir);
            in_place_evaluate_solution(solution, weights);
            new_solutions.push(solution);
        }
        s.is_done = true;
    });
    solutions = solutions.concat(new_solutions);
    solutions.sort(function(a, b) { return b.weight - a.weight; });
    return solutions.slice(0, MAX_SOLUTIONS_COUNT);
}

然後   can_move_orb_in_solution 

function can_move_orb_in_solution(solution, dir) {
    // Don't allow going back directly. It's pointless.  把直接退後的solution remove
    if (solution.path[solution.path.length-1] == (dir + 4) % 8) {
        return false;
    }
    return can_move_orb(solution.cursor, dir);
}

然後  can_move_orb() 的定義如下 :

function can_move_orb(rc, dir) {
    switch (dir) {
        case 0: return                    rc.col < COLS-1;
        case 1: return rc.row < ROWS-1 && rc.col < COLS-1;
        case 2: return rc.row < ROWS-1;
        case 3: return rc.row < ROWS-1 && rc.col > 0;
        case 4: return                    rc.col > 0;
        case 5: return rc.row > 0      && rc.col > 0;
        case 6: return rc.row > 0;
        case 7: return rc.row > 0      && rc.col < COLS-1;
    }
    return false;
}

col ->  y
row -> x

0  -> top ,  1 -> right and top ,  2 -> right,  3 -> right and down,  4 -> down
5  ->  left and down,   6 -> left ,     7  ->
           
 7    0    1
 6    p    2
 5    4    3

再來看   in_place_swap_orb_in_solution()

function in_place_swap_orb_in_solution(solution, dir) {
    var res = in_place_swap_orb(solution.board, solution.cursor, dir);
    solution.cursor = res.rc;
    solution.path.push(dir);
}

再來看     in_place_swap_orb()

function in_place_swap_orb(board, rc, dir) {
    var old_rc = copy_rc(rc);
    in_place_move_rc(rc, dir);
    var orig_type = board[old_rc.row][old_rc.col];
    board[old_rc.row][old_rc.col] = board[rc.row][rc.col];
    board[rc.row][rc.col] = orig_type;
    return {board: board, rc: rc};
}
 

function find_matches(board) {
    var match_board = create_empty_board();

    // 1. filter all 3+ consecutives.
    //  (a) horizontals
    for (var i = 0; i < ROWS; ++ i) {
        var prev_1_orb = 'X';
        var prev_2_orb = 'X';
        for (var j = 0; j < COLS; ++ j) {
            var cur_orb = board[i][j];
            if (prev_1_orb == prev_2_orb && prev_2_orb == cur_orb && cur_orb != 'X') {
                match_board[i][j] = cur_orb;
                match_board[i][j-1] = cur_orb;
                match_board[i][j-2] = cur_orb;
            }
            prev_1_orb = prev_2_orb;
            prev_2_orb = cur_orb;
        }
    }
    //  (b) verticals
    for (var j = 0; j < COLS; ++ j) {
        var prev_1_orb = 'X';
        var prev_2_orb = 'X';
        for (var i = 0; i < ROWS; ++ i) {
            var cur_orb = board[i][j];
            if (prev_1_orb == prev_2_orb && prev_2_orb == cur_orb && cur_orb != 'X') {
                match_board[i][j] = cur_orb;
                match_board[i-1][j] = cur_orb;
                match_board[i-2][j] = cur_orb;
            }
            prev_1_orb = prev_2_orb;
            prev_2_orb = cur_orb;
        }
    }

    var scratch_board = copy_board(match_board);

    // 2. enumerate the matches by flood-fill.
    var matches = [];
    for (var i = 0; i < ROWS; ++ i) {
        for (var j = 0; j < COLS; ++ j) {
            var cur_orb = scratch_board[i][j];
            if (typeof(cur_orb) == 'undefined') { continue; }
            var stack = [make_rc(i, j)];
            var count = 0;
            while (stack.length) {
                var n = stack.pop();
                if (scratch_board[n.row][n.col] != cur_orb) { continue; }
                ++ count;
                scratch_board[n.row][n.col] = undefined;
                if (n.row > 0) { stack.push(make_rc(n.row-1, n.col)); }
                if (n.row < ROWS-1) { stack.push(make_rc(n.row+1, n.col)); }
                if (n.col > 0) { stack.push(make_rc(n.row, n.col-1)); }
                if (n.col < COLS-1) { stack.push(make_rc(n.row, n.col+1)); }
            }
            matches.push(make_match(cur_orb, count));
        }
    }

    return {matches: matches, board: match_board};
}

function make_rc(row, col) {
    return {row: row, col: col};
}


function in_place_evaluate_solution(solution, weights) {
    var current_board = copy_board(solution.board);
    var all_matches = [];
    while (true) {
        var matches = find_matches(current_board);
        if (matches.matches.length == 0) {
            break;
        }
        in_place_remove_matches(current_board, matches.board);
        in_place_drop_empty_spaces(current_board);
        all_matches = all_matches.concat(matches.matches);
    }
    solution.weight = compute_weight(all_matches, weights);
    solution.matches = all_matches;
    return current_board;
}


//    rc ->  代表目前的   (x,y)   ,  dir -> 要移動的方向....  這個function 計算出下個點的col,row
function in_place_move_rc(rc, dir) {
    switch (dir) {
        case 0:              rc.col += 1; break;
        case 1: rc.row += 1; rc.col += 1; break;
        case 2: rc.row += 1;              break;
        case 3: rc.row += 1; rc.col -= 1; break;
        case 4:              rc.col -= 1; break;
        case 5: rc.row -= 1; rc.col -= 1; break;
        case 6: rc.row -= 1;              break;
        case 7: rc.row -= 1; rc.col += 1; break;
    }
}


//   update  掉下來的 code
function in_place_drop_empty_spaces(board) {
    for (var j = 0; j < COLS; ++ j) {
        var dest_i = ROWS-1;
        for (var src_i = ROWS-1; src_i >= 0; -- src_i) {
            if (board[src_i][j] != 'X') {
                board[dest_i][j] = board[src_i][j];
                -- dest_i;
            }
        }
        for (; dest_i >= 0; -- dest_i) {
            board[dest_i][j] = 'X';
        }
    }
    return board;
}


舉例  

src       -dest
0 O2
1 O1
2 O0      O2
3 X        O1
4 X        O0

src 從 4 開始  (row-1)
一開始  board[3][0]  為  X,      if (board[src_i][j] != 'X') 不成立

src 變 3
            board[3][0]  為  X,        if (board[src_i][j] != 'X') 不成立

src 變 2
            board[2][0]  為  O0,        if (board[src_i][j] != 'X') 成立

  dest 為 row            
board[dest-i][0] = O0  

依序下去....  就會造成消去的現象


function in_place_remove_matches(board, match_board) {
    for (var i = 0; i < ROWS; ++ i) {
        for (var j = 0; j < COLS; ++ j) {
            if (typeof(match_board[i][j]) != 'undefined') {
                board[i][j] = 'X';  ///  代表可以消去的
            }
        }
    }
    return board;
}


function compute_weight(matches, weights) {
    var total_weight = 0;
    matches.forEach(function(m) {
        var base_weight = weights[m.type][m.count >= 5 ? 'mass' : 'normal'];
        var multi_orb_bonus = (m.count - 3) * MULTI_ORB_BONUS + 1;
        total_weight += multi_orb_bonus * base_weight;
    });
    var combo_bonus = (matches.length - 1) * COMBO_BONUS + 1;
    return total_weight * combo_bonus;
}

 Normal (3+)
 Mass (5+)


當按下  rand 的 button

$('#randomize').click(function() {
        var types = $('#randomization-type').val().split(/,/);
        $('#grid > div').each(function() {
            var index = Math.floor(Math.random() * types.length); //
            show_element_type($(this), types[index]);
        });
        clear_canvas();
    });

 舉例來說....  當你選擇    5 + Heal  ,  randomization-type ->  "0,1,2,3,4,5"  ....

types.length ->  6

<select id="randomization-type">
        <option value="0,1,2">3-color</option>
        <option value="0,1,2,5">3 + Heal</option>
        <option value="0,1,2,3,4">5-color</option>
        <option value="0,1,2,3,4,5" selected="selected">5 + Heal</option>
        <option value="0,1,2,3,4,5,6">All</option>
    </select>

當按下  clear 的按鈕.... 把每個方格填入  'X'  ->  也就是問號....
 
 $('#clear').click(function() {
        $('#grid > div').each(function() { show_element_type($(this), 'X'); });
        clear_canvas();
    });

當按下   drop  按鈕 ....
$('#drop').click(function() {
        var solution = global_solutions[global_index];
        if (!solution) {
            return;
        }
        var board = in_place_evaluate_solution(solution, get_weights());
        show_board(board);
        clear_canvas();
    });


從   global_solution  抓出 solution......  


當按下   solve ...

 $('#solve').click(function() {
        var solver_button = this;
        var board = get_board();
        global_board = board;
        solver_button.disabled = true;
        solve_board(board, function(p, max_p) {
            $('#status').text('Solving (' + p + '/' + max_p + ')...');
        }, function(solutions) {
            var html_array = [];
            solutions = simplify_solutions(solutions);
            global_solutions = solutions;
            solutions.forEach(function(solution) {
                add_solution_as_li(html_array, solution, board);
            });
            $('#solutions > ol').html(html_array.join(''));
            solver_button.disabled = false;
        });
    });


//   給定新的dir...  然後和原本的互換....
function in_place_swap_orb(board, rc, dir) {
    var old_rc = copy_rc(rc);
    in_place_move_rc(rc, dir);
    var orig_type = board[old_rc.row][old_rc.col];
    board[old_rc.row][old_rc.col] = board[rc.row][rc.col];
    board[rc.row][rc.col] = orig_type;
    return {board: board, rc: rc};
}

//    呼叫剛剛   in_place_swap_orb   ...  然後把  dir push 到  path queue裡面....
function in_place_swap_orb_in_solution(solution, dir) {
    var res = in_place_swap_orb(solution.board, solution.cursor, dir);
    solution.cursor = res.rc;
    solution.path.push(dir);
}

function in_place_evaluate_solution(solution, weights) {
    var current_board = copy_board(solution.board);
    var all_matches = [];
    while (true) {
        var matches = find_matches(current_board);
        if (matches.matches.length == 0) {
            break;
        }
        in_place_remove_matches(current_board, matches.board);
        in_place_drop_empty_spaces(current_board);
        all_matches = all_matches.concat(matches.matches);
    }
    solution.weight = compute_weight(all_matches, weights);
    solution.matches = all_matches;
    return current_board;
}


//   遞迴....自己 call 自己
function solve_board_step(solve_state) {
    if (solve_state.p >= solve_state.max_length) {   //  結束條件
        solve_state.finish_callback(solve_state.solutions);
        return;
    }

    ++ solve_state.p;
    solve_state.solutions = evolve_solutions(solve_state.solutions,
                                             solve_state.weights,
                                             solve_state.dir_step);
    solve_state.step_callback(solve_state.p, solve_state.max_length);

    setTimeout(function() { solve_board_step(solve_state); }, 0);
}


//   感覺就是8各方向都去計算出來 score.....然後利用排序找出最佳的方向...
//   然後只留下那個方向....

function evolve_solutions(solutions, weights, dir_step) {
    var new_solutions = [];
    solutions.forEach(function(s) {
        if (s.is_done) {
            return;
        }
        for (var dir = 0; dir < 8; dir += dir_step) {
            if (!can_move_orb_in_solution(s, dir)) {
                continue;
            }
            var solution = copy_solution(s);
            in_place_swap_orb_in_solution(solution, dir);
            in_place_evaluate_solution(solution, weights);
            new_solutions.push(solution);
        }
        s.is_done = true;
    });
    solutions = solutions.concat(new_solutions);
    solutions.sort(function(a, b) { return b.weight - a.weight; });
    return solutions.slice(0, MAX_SOLUTIONS_COUNT);
}




2016年5月31日 星期二

install opencv on rpi3



Ref : http://www.pyimagesearch.com/2016/04/18/install-guide-raspberry-pi-3-raspbian-jessie-opencv-3/

3D scan code study , step 3 and step 4


step3 :  threshold

=============================================
import numpy as np
import cv2
horzlino=1920
vertlino=1080
import os
import glob
img_names = glob.glob("CAML/*.png")
img1=cv2.imread(img_names[0],cv2.IMREAD_GRAYSCALE)
ii=50
# adjusting the threshold for processing area and eliminating shadows
# use left and right arrow key to adjust and press 'q' when finished

while True:
    ret,img1th = cv2.threshold(img1,ii,255,cv2.THRESH_TOZERO)
    cv2.putText(img1th,"Threshold is "+str(ii), (10,50), cv2.FONT_HERSHEY_SIMPLEX, 2, 255)
    cv2.imshow("PWindow2",img1th)
    k = cv2.waitKey(0)
    if k == ord('q'):
        break
    elif k == ord('a'):
        ii=ii+1
    elif k == ord('b'):
        ii=ii-1

cv2.destroyAllWindows()

np.save(captdirect+"/"+"thresholdleft" , ii)
 
img_names = glob.glob("CAMR/*.png")
img1=cv2.imread(img_names[0],cv2.IMREAD_GRAYSCALE)
ii=50
while True:
    ret,img1th = cv2.threshold(img1,ii,255,cv2.THRESH_TOZERO)
    cv2.putText(img1th,"Threshold is "+str(ii), (10,50), cv2.FONT_HERSHEY_SIMPLEX, 2, 255)
    cv2.imshow("PWindow2",img1th)
    k = cv2.waitKey(0)
    if k == ord('q'):
        break
    elif k == ord('a'):
        ii=ii+1
    elif k == ord('b'):
        ii=ii-1
     

cv2.destroyAllWindows()
np.save("thresholdright" , ii)  #  Save an array to a binary file in NumPy .npy format.
print 'Threshold Done!'


================================================================

step4 :  calcxy1xy2

================================================================


import numpy as np
import cv2
import os
import glob
old_settings = np.seterr(all='ignore')

horzlino=1920
vertlino=1080


Direct="CAMR/"
rightcamcode=np.load(Direct+"coloccod.npy" )  #  step 2  result,  right camcode,1920x1080x2 維度

                                                                                    [x][y][0] -> 放水平方向的 gray code 影像
                                                                                    [x][y][1] -> 放垂直方向的 gray code 影像

Direct="CAML/"
leftcamcode=np.load(Direct+"coloccod.npy" )   # step 2 result,  left camcode

thresholdleft=np.load("thresholdleft.npy" )        #  step 3 output thr val
thresholdright=np.load("thresholdright.npy" )    # step 3 output thr val

imgmaskrightf ="CAMR/CAM001.png"
img1=cv2.imread(imgmaskrightf,cv2.IMREAD_GRAYSCALE)  # Loads image in grayscale mode
ret,img1 = cv2.threshold(img1,thresholdright,255,cv2.THRESH_TOZERO) # TOZERO .. 小於thr 變成黑色...大於thr 維持不變
imgmaskright=np.divide(img1,img1)

=======================================================================
#   np.divide  

numpy.divide(x1x2[out]) = <ufunc 'divide'>
Divide arguments element-wise.
Parameters:
x1 : array_like
Dividend array.
x2 : array_like
Divisor array.
out : ndarray, optional
Array into which the output is placed. Its type is preserved and it must be of the right shape to hold the output. See doc.ufuncs.
Returns:
y : ndarray or scalar
The quotient x1/x2, element-wise. Returns a scalar if both x1 andx2 are scalars.
np.divide(img1,img1)    -> 得到mask 只有0或是1 的影像
===================================================================
imgmaskleftf ="CAML/CAM101.png"
img1=cv2.imread(imgmaskleftf,cv2.IMREAD_GRAYSCALE)
ret,img1 = cv2.threshold(img1,thresholdleft,255,cv2.THRESH_TOZERO)
imgmaskleft=np.divide(img1,img1)

#  換成左邊做一次


kkl=0                #  kk coef for left
kkr=0                #  kk coef for right
colocright=[]     #  color array for right
colocleft=[]       #  color array for left
leftsrt=[]
rightsrt=[]

# finding similar points in both camera bast on projected pattern
for ii in range(0, horzlino):   # horzlino->  1920
    for jj in range(0, vertlino):  # vertlino->  1080
        if (rightcamcode[jj][ii][0]!=0 and rightcamcode[jj][ii][1]!=0 and imgmaskright[jj][ii]!=0):
           colocright.append(np.uint32([rightcamcode[jj][ii][0]+rightcamcode[jj][ii][1]*1024 ,ii, jj]))    
           #colocright 為一維陣列...  一次加三個值... color val,x , y
           rightsrt.append(rightcamcode[jj][ii][0]+rightcamcode[jj][ii][1]*1024)
           kkl=kkl+1
        if (leftcamcode[jj][ii][0]!=0 and leftcamcode[jj][ii][1]!=0 and imgmaskleft[jj][ii]!=0):
           colocleft.append(np.uint32([leftcamcode[jj][ii][0]+leftcamcode[jj][ii][1]*1024,ii ,jj]))
           leftsrt.append(leftcamcode[jj][ii][0]+leftcamcode[jj][ii][1]*1024)
           kkr=kkr+1

print kkr,kkl
np.savetxt("leftcod" , colocleft ,fmt='%d', delimiter=', ', newline='\n')
np.savetxt("rightcod" , colocright ,fmt='%d', delimiter=', ', newline='\n')

#  
numpy.savetxt(fnameXfmt='%.18e'delimiter=' 'newline='\n'header=''footer='',comments='# ')
fname : filename or file handle
If the filename ends in .gz, the file is automatically saved in compressed gzip format. loadtxt understands gzipped files transparently.
X : array_like

Data to be saved to a text file.


import operator
colocrightsrt=[]
colocleftsrt=[]
colocrightsrt=sorted(colocright, key=operator.itemgetter(0))
colocleftsrt=sorted(colocleft, key=operator.itemgetter(0))
rightsrtt=sorted(rightsrt)
leftsrtt=sorted(leftsrt)

================================

numpy.sort

numpy.sort(aaxis=-1kind='quicksort'order=None)
Parameters:
a : array_like
Array to be sorted.
axis : int or None, optional
Axis along which to sort. If None, the array is flattened before sorting. The default is -1, which sorts along the last axis.
kind : {‘quicksort’, ‘mergesort’, ‘heapsort’}, optional
Sorting algorithm. Default is ‘quicksort’.
order : str or list of str, optional
When a is an array with fields defined, this argument specifies which fields to compare first, second, etc. A single field can be specified as a string, and not all fields need be specified, but unspecified fields will still be used, in the order in which they come up in the dtype, to break ties.
Returns:
sorted_array : ndarray
Array of the same type and shape as a.
===========================
kkr=0
np.save("colocrightsrt" , colocrightsrt)
np.save("colocleftsrt" , colocleftsrt)
newlistl=np.unique(leftsrtt)
np.savetxt("colocleftsrtuniq" , newlistl ,fmt='%d', delimiter=', ', newline='\n')
newlistr=np.unique(rightsrtt)
np.savetxt("colocrightsrtuniq" , newlistr ,fmt='%d', delimiter=', ', newline='\n')

#finding common points in both cameras
camunio=np.intersect1d(newlistl,newlistr)


numpy.intersect1d(ar1ar2assume_unique=False)[source]
Find the intersection of two arrays.
Return the sorted, unique values that are in both of the input arrays.
Parameters:
ar1, ar2 : array_like
Input arrays.
assume_unique : bool
If True, the input arrays are both assumed to be unique, which can speed up the calculation. Default is False.
Returns:
intersect1d : ndarray
Sorted 1D array of common and unique elements.
>>> np.intersect1d([1, 3, 4, 3], [3, 1, 2, 1])
array([1, 3])


kkl=0
kkr=0
kk=0
matchpixels=np.zeros((np.size(camunio),4), dtype=np.int16)
matchpixels[kk][0]=0
matchpixels[kk][1]=0
matchpixels[kk][2]=0
matchpixels[kk][3]=0
for i in camunio:
    while (newlistr[kkr] != i):
        kkr=kkr+1
        matchpixels[kk][0]=colocrightsrt[kkr][1]  # right camera x pixel coordinate
        matchpixels[kk][1]=colocrightsrt[kkr][2]  # right camera y pixel coordinate
    while (newlistl[kkl] != i):
        kkl=kkl+1
        matchpixels[kk][2]=colocleftsrt[kkl][1]   #left camera x pixel coordinate
        matchpixels[kk][3]=colocleftsrt[kkl][2]   #left camera y pixel coordinate
    kk=kk+1
 

# 找到相同的亮度的座標...記錄left camera 的 (x,y) 放到  matchpixel[0][1]
                                                記錄right camera 的 (x,y) 放到  matchpixel[2][3]

np.savetxt("colocuniq" , matchpixels ,fmt='%d', delimiter=',', newline='\n')
print 'calcxy1xy2 Done!'




2016年5月30日 星期一

3D scan 的參考資料



Ref : http://arxiv.org/pdf/1406.6595v1.pdf

Ref : http://www.csksoft.net/blog/post/lowcost_3d_laser_ranger_1.html

3D scan code study step 1 and step 2

SL3DS1.projcapt.py
=================================================
import cv2
import sys

CV_CAP_PROP_BRIGHTNESS=10
CV_CAP_PROP_CONTRAST=11
CV_CAP_PROP_SATURATION=12
CV_CAP_PROP_EXPOSURE=15
CV_CAP_PROP_WHITE_BALANCE=17
## video capture right camera
video_capture0 = cv2.VideoCapture(0)
video_capture0.set(CV_CAP_PROP_BRIGHTNESS,30.0)
video_capture0.set(CV_CAP_PROP_CONTRAST,5.0)
video_capture0.set(CV_CAP_PROP_SATURATION,100.0)
video_capture0.set(CV_CAP_PROP_EXPOSURE,-8.0)
video_capture0.set(CV_CAP_PROP_WHITE_BALANCE,10000.0)

print video_capture0.get(4)
video_capture0.set(3,1920.0)   ## horizontal pixels
video_capture0.set(4,1080.0)  ##  vertical pixels
print video_capture0.get(4)    ##  check vertical pixel

##video capture left camera
video_capture1 = cv2.VideoCapture(1)
print video_capture1.get(4)
video_capture1.set(3,1920.0)
video_capture1.set(4,1080.0)
print video_capture1.get(4)
print video_capture1.get(9)

video_capture1.set(CV_CAP_PROP_BRIGHTNESS,30.0)
video_capture1.set(CV_CAP_PROP_CONTRAST,5.0)
video_capture1.set(CV_CAP_PROP_SATURATION,100.0)
video_capture1.set(CV_CAP_PROP_EXPOSURE,-8.0)
video_capture1.set(CV_CAP_PROP_WHITE_BALANCE,10000.0)

## camera properties in opencv
##CV_CAP_PROP_FRAME_WIDTH Width of the frames in the video stream.
##CV_CAP_PROP_FRAME_HEIGHT Height of the frames in the video stream.
##CV_CAP_PROP_FPS Frame rate.
##CV_CAP_PROP_FORMAT Format of the Mat objects returned by retrieve() .
##CV_CAP_PROP_MODE Backend-specific value indicating the current capture mode.
##CV_CAP_PROP_BRIGHTNESS Brightness of the image (only for cameras).
##CV_CAP_PROP_CONTRAST Contrast of the image (only for cameras).
##CV_CAP_PROP_SATURATION Saturation of the image (only for cameras).
##CV_CAP_PROP_HUE Hue of the image (only for cameras).
##CV_CAP_PROP_GAIN Gain of the image (only for cameras).
##CV_CAP_PROP_EXPOSURE Exposure (only for cameras).
##CV_CAP_PROP_CONVERT_RGB Boolean flags indicating whether images should be converted to RGB.
##CV_CAP_PROP_WHITE_BALANCE Currently unsupported
##CV_CAP_PROP_RECTIFICATION Rectificat


#show frames from cameras
ret, frame0 = video_capture0.read()
cv2.imshow("cam0", frame0)
ret, frame1 = video_capture1.read()
cv2.imshow("cam1", frame1)
cv2.waitKey(3000)

ret, frame0 = video_capture0.read()
cv2.waitKey(100)
##    cv2.imshow("cam0", frame0)
ret, frame1 = video_capture1.read()
cv2.waitKey(100)

cv2.destroyAllWindows()
horzlino=1280
vertlino=720
for ii in range(0,19):
    print ii, video_capture1.get(ii)
## open a borderless window for showing projector images as a second display  
cv2.namedWindow("Projector Window",cv2.WND_PROP_FULLSCREEN )
cv2.setWindowProperty("Projector Window", 0,1)
cv2.resizeWindow("Projector Window", 1024,768)
cv2.moveWindow("Projector Window", 1025, -2)

## folders for saving images of left and right cameras
import os
try:
    os.makedirs(textvalcap+"/"+'CAMR')
except OSError:
    pass

try:
    os.makedirs(textvalcap+"/"+'CAML')
except OSError:
    pass

imggray=np.load('proj.npy')
# Capture images from left and right cameras after showing pattern in projector
for x in range(1, 43):  # processing 42 to left and right 
    cv2.imshow("Projector Window",imggray[:,:,x-1])
    filename0 = 'CAMR/CAM0%02d.png'%(x,)
    filename1 = 'CAML/CAM1%02d.png'%(x,)
    print filename0
    ret, frame0 = video_capture0.read()
    cv2.waitKey(100)
    ret, frame1 = video_capture1.read()
    cv2.waitKey(100)
    cv2.imwrite(filename0,frame0)
    cv2.imwrite(filename1,frame1)

video_capture0.release()
video_capture1.release()
# When everything is done, release the capture
cv2.destroyAllWindows()
print 'pcapture Done!'


========================================================
step 2 : Processing the 42 images of each camera and capture points codes
========================================================
import numpy as np
import cv2
import os
import glob

def imgdesig(img1,img2): #define a function for getting dark and light pattern
   old_settings = np.seterr(all='ignore')
   img1=cv2.imread(img1,cv2.IMREAD_GRAYSCALE)  # 轉成  灰階
   ret,img1 = cv2.threshold(img1,10,255,cv2.THRESH_TOZERO) # thr -> 10,  max_val -> 255
   img2=cv2.imread(img2,cv2.IMREAD_GRAYSCALE)  # 轉成  灰階
   ret,img2 = cv2.threshold(img2,10,255,cv2.THRESH_TOZERO)  # thr -> 10,  max_val -> 255
   img12=(((img1//2)+(img2//2)))  #  sum of img1/2 + img2/2
   img123=(np.divide(img1,img12))
   img123=(np.divide(img123,img123))
   return img123     #  會得到  img1 or (img2 + img1)/2 的影像, 而且只有 0和1的影像

#   np.divide ->   The quotient x1/x2, element-wise. Returns a scalar if both x1 andx2 are scalars
horzlino=1920
vertlino=1080
Direct=captdirect+"/"+"CAMR/"
img_names = glob.glob(Direct+"*.png")
print captdirect
#call for processing the right camera images
execfile("camlcoloc.py")
np.save(Direct+"coloccod" , rightcamcode)
cv2.waitKey(200)
Direct=captdirect+"/"+"CAML/"
img_names = glob.glob(Direct+"*.png")
#call for processing the left camera images
execfile("camlcoloc.py")
np.save(Direct+"coloccod" , rightcamcode)

cv2.destroyAllWindows()
print 'Procimg Done!'

============

camlcoloc.py
============

#  horzlino = 1920,   vertlino = 1080
grayimg=np.zeros((vertlino, horzlino), dtype=np.int16)
rightcamcode=np.zeros((vertlino, horzlino,2), dtype=np.int16)
##=======================================================
#Horizontal gray code
for ii in range(3,22,2):

#range([start], stop[, step])
  • start: Starting number of the sequence.
  • stop: Generate numbers up to, but not including this number.
  • step: Difference between each number in the sequence.

    xx=ii-3
    xx=xx//2
    filename1 =  img_names[ii]
    filename2 =  img_names[ii-1]
    ff=imgdesig(filename1,filename2)  # ff 為 img
    print 'processing %s...' % filename1, (2**xx)
    grayimg=grayimg+(2**xx)*ff   

#  當  xx -> 0    (2**xx) ->  1       3
#  當  xx -> 1    (2**xx) ->  2       5
#  當  xx -> 2    (2**xx) ->  4        7
#  當  xx -> 3    (2**xx) ->  8         9
#  當  xx -> 4    (2**xx) ->  16       11
#  當  xx -> 5    (2**xx) ->  32       13
#  當  xx -> 6    (2**xx) ->  64       15
#  當  xx -> 7    (2**xx) ->  128     17
#  當  xx -> 8    (2**xx) ->  256      19
#  當  xx -> 9    (2**xx) ->  512      21

    

imgbin3=np.zeros((vertlino, horzlino,3), dtype=np.uint8)
for ii in range(0, horzlino):
    for jj in range(0, vertlino):
        rightcamcode[jj][ii][0]=grayimg[jj][ii]
        imgbin3[jj][ii][1]= grayimg[jj][ii]%256
        imgbin3[jj][ii][2]= 40*grayimg[jj][ii]//256
        imgbin3[jj][ii][0]= 4
img1=(grayimg%255)
cv2.imshow("PWindow2",imgbin3)
cv2.waitKey(100)

##=======================================================
#Vertical gray code

img1=cv2.imread(img_names[0],cv2.IMREAD_GRAYSCALE)
grayimg=(img1*0)+1023
grayimg=grayimg*0    #  把  grayimg 清除為0
for ii in range(23,42,2):
    xx=ii-22
    xx=xx//2
    filename1 =  img_names[ii]
    filename2 =  img_names[ii-1]
    ff=imgdesig(filename1,filename2)
    print 'processing %s...' % filename1, (2**xx)
    grayimg=grayimg+(2**xx)*ff

for ii in range(0, horzlino):
    for jj in range(0, vertlino):
        rightcamcode[jj][ii][1]=grayimg[jj][ii]
        imgbin3[jj][ii][0]= (imgbin3[jj][ii][0]+grayimg[jj][ii]%256)%256
        imgbin3[jj][ii][2]= 40*(imgbin3[jj][ii][2]+grayimg[jj][ii]%256)//80
        imgbin3[jj][ii][1]= 4
img1=(grayimg%255)
cv2.imshow("PWindow2",imgbin3)
cv2.waitKey(2000)



3D scan 研究筆記 00








1. Projecting gray patterns and capturing images from two cameras "SL3DS1.projcapt.py"
2. Processing the 42 images of each camera and capture points codes " SL3DS2.procimages.py"
3. Adjusting threshold to select masking for areas to be processed "SL3DS3.adjustthresh.py"
4. Find and save similar points in each camera "SL3DS4.calcpxpy.py"
5 Calculate X,Y and Z coordinates of point cloud "SL3DS5.calcxyz.py"
The output is a PLY file with coordinate and color information of points on object surface. You can open PLY files with CAD software like Autodesk products or an open source sofware like Meshlab.
 I have also developed a GUI for this software in TKINTER that you can find in step six with two sample data sets . You can find additional information on this subject on the following websites:




Hardware consists of :
1. Two webcameras (Logitech C920C)
2. Infocus LP330 projector
3. Camera and projector stand (made from 3 mm Acrylic plates and 6 mm HDF wood cut with a laser cutter)
The projector is an Infocus LP330 (Native resolution 1024X768) with following specs.
Brightness:650 Lumens 
Color Light Output:**Contrast (Full On/Off):400:1 
Auto Iris:No 
Native Resolution:1024x768 
Aspect Ratio:4:3 (XGA) 
Video Modes:**
Data Modes:MAX 1024x768 
Max Power:200 Watts 
Voltage:100V - 240V 
Size(cm) (HxWxD):6 x 22 x 25 
Weight:2.2 kg 
Lamp Life(Full Power):1,000 hours 
Lamp Type:UHPLamp 
Wattage:120 Watts 
Lamp Quantity:1 
Display Type:2 cm DLP (1) 
Standard Zoom Lens:1.25:1 
Focus:Manual 
Throw Dist (m): 1.5 - 30.5 
Image Size(cm):76 - 1971

This video projector is used to project structured light patterns on the object to be scanned. The structured pattern consists of vertical and horizontal white light strips that are saved on a data file and webcams capture those distorted strips.
Preferably use those cameras that are software controllable because you need to adjust focus, brightness, resolution and image quality. It is possible to use DSLR cameras with SDKs that are provided by each brand.
Assembly and tests were conducted in Copenhagen Fablab with its support.

For testing the 3d scan software in this step I add two data sets one is scan of a fish and another is just a plane wall to see the accuracy of it. Open ZIP files and run SL3DGUI.py. For installation check step 2.
For using 3d scan part you need to install two cameras and projector but for other parts just click on the button. For testing the sample data first click on process then threshold, stereo match and finally point cloud. Install Meshlab to see the point cloud.


Q & A :

Q:how do you convert gray code to decimal?
A:Each pixel in projector screen has a horizontal and a vertical gray code, by grayimg=grayimg+(2**xx)*ff in a loop I convert it to decimal. Horizontal and vertical decimal code is then combined to get a unique number for each pixel among 1024*768 pixel of the projector.
colocright.append(np.uint32([rightcamcode[jj][ii][0]+rightcamcode[jj][ii][1]*1024 ,ii, jj])) is the line that combines horizontal and vertical so that global code is equal to horizontal code plus vertical code multiplied with 1024

file "graykod" is my gray to decimal conversion
rightcod and leftcod files have three columns, first column is the decimal code related to projector pixel, second is horizontal pixel coordinate of the camera and third is vertical pixel coordinate of the camera
aa=a[a[:,0].argsort(),] is for sorting based on the decimal code of projector pixels so that makes it easy to find similar projector pixels in left and right camera
if you need more explanation please ask, also please read the references precisely before asking more questions.
Later you can send me your code for checking and debuging. Try to run programs with the fish and wall data


=================================================================

More specifically, the decoding is performed as follows:
 • Determine whether a pixel p is lit or not (1 or 0) in the images capturing the projected sequence of patterns encoding the columns.
 • Calculate its binary form Bp.
 • Convert the binary form Bp into the equivalent decimal number x.
Similarly, the process is repeated for the images capturing the projected sequence of patterns encoding the rows and results in a decimal number y. Thus, (x, y) are the image coordinates of the projector’s pixel corresponding to the pixel being decoded in a camera. By repeating the process for all the cameras, we can map the pixels not only to the projector’s pixels but rather to the other cameras viewing the object. It should be noted that a camera pixel may be mapped to more than one pixels of another camera due to differences between the camera and projector resolutions.

The decoded captured images result in a set of a many-to-many mappings between the pixels of the different cameras. Next, by triangulating the rays corresponding to each pair, a 3D point is computed at their intersection. The projection of this point falls onto the mapped pixels in the different cameras.


file "graykod" is my gray to decimal conversion

rightcod and leftcod files have three columns, first column is the decimal code related to projector pixel, second is horizontal pixel coordinate of the camera and third is vertical pixel coordinate of the camera
aa=a[a[:,0].argsort(),] is for sorting based on the decimal code of projector pixels so that makes it easy to find similar projector pixels in left and right camera

pixel size and focal length are parameters that you should get from camera manufacturer. x0l,x0r,y0l,y0r,z0l,z0r phi and tet are values that should be set based on your geometrical design(installation of cameras)

For calibration you need accurate calibration board and good algorithm. I used a calibration board from a commercial 3d scanner and found out that my settings are accurate enough for me. But if you need accuracy of 100th of millimeter you need calibration. A good calibration board costs more than 1000 Euros!! 
I made camera base and stands with a laser cutter so I could have accurate installation of cameras without calibration.

img12=(((img1//2)+(img2//2)))
img123=(np.divide(img1,img12))
img123=(np.divide(img123,img123))
Above part is for thresholding the images and make a binary black and white image read page six of this document:
imgbin3[jj][ii][1]= grayimg[jj][ii]%256
imgbin3[jj][ii][2]= 40*grayimg[jj][ii]//256
imgbin3[jj][ii][0]= 4
this part is just for demonstration of stripes with different color, so you can omit this part.



Ref : http://www.instructables.com/id/DIY-3D-scanner-based-on-structured-light-and-stere/?ALLSTEPS

2016年5月3日 星期二

使用 imagewriter 來備份映像檔





先按開啟....  給定你要儲存的地方和檔名



按下read..  就會立即把記憶卡的image 備份到你的電腦...