"尋龍點穴:陰宅墳山選址真鑑"的計算機視覺算法+python實現
尋龍點穴是陰宅風水的重中之中,指的是根據山脈水勢尋找棺木埋葬的最優點;因此可以形式化地描述爲在一個圖片中尋找某種規則約束下的最優點的問題(約束下空間最優點搜索);我們採用代數和計算機視覺結合的方法來嘗試實現"尋龍點穴";
當然,由於水平有限,以及古籍和現代算法描述的差異,不能完全符合原生方法的精髓;當前沒有放出全部代碼,因爲個別部分還在調試(誰能幫我解決如何使用python疊加透明png圖片…)
TO DO:
入 山 尋 水 口
登 穴 看 明 堂
----------------- << 尋龍點穴 . 陰宅墳山選址祕籍 >>
數據集的製作
我收集了各異的100張山體圖片,製作成的尺寸,如果有需要,可以下載:
鏈接:https://pan.baidu.com/s/1WnqoTxNEAro9Y8wWIi0COw
提取碼:nltc
圖像區域分割
由於需要根據山體水勢選址,首先第一步就是需要對圖片的像素點進行分類:那些屬於水域,那些屬於山體,那些屬於天空;這是計算機視覺裏的圖像語義分割,已經有無數依賴神經網絡的算法把這個問題做得很完美了,你可以下載一個pre-trained的model來做,我這裏自己寫了一個粗暴的像素閾值劃分來分類:
def process_img_contrastly(IMG_MAT):
IMG_MAT_SIN = (IMG_MAT[:,:,0] + IMG_MAT[:,:,1])/2;
IMG_MAT_MAX = IMG_MAT_SIN.max();
IMG_MAT_MIN = IMG_MAT_SIN.min();
IMG_MAT_AVE = (IMG_MAT_MAX + IMG_MAT_MIN)/2;
# 標記山體像素點;
IMG_MAT_PROCESSED = IMG_MAT;
for i in range(len(IMG_MAT_SIN)):
for j in range(len(IMG_MAT_SIN[i])):
if IMG_MAT_SIN[i][j]>IMG_MAT_AVE*1.4:
IMG_MAT_PROCESSED[i][j][0] = 15;
IMG_MAT_PROCESSED[i][j][1] = 33;
IMG_MAT_PROCESSED[i][j][2] = 77;
return IMG_MAT_PROCESSED;
在圖像中搜索
首先考慮我們把圖像劃分爲離散的區塊(比如的尺寸圖片劃分爲的區塊分佈),然後我們在這些區塊裏搜尋目標的明堂節點和水口節點並最終在圖片上標註出來;
現在定義一個點到點的轉移符號,並且在轉移的方式上定義它們的複合操作:
注意這裏可以等於;
那麼事實上我們可以定義一個空間點轉移羣記作,其中就是在節點之間上下左右以不定的步長移動的動作,而就是這些動作的複合,並且約定(幺元是唯一的);
現在算法分兩大步:
-
搜索山體的均勻質點:從初始節點開始找一個儘量落在山體質心的區域;
-
搜索最優的下葬區域:從山體質心開始找一個符合尋龍方法規則的最優點;
因此其實兩步都可以抽象爲圖搜索+規則判定,我們採用廣度優先搜索(BFS)+規則判定來實現;
首先我們定義點以及變換,注意點可以和變換運算,變換之間也可以運算;
// -------------------- 定義節點和轉移算子 -------------------
class Node{
private:
int POS_x,POS_y;
public:
bool IN_HILL; // 具體值在構造函數中賦值;
Node(int POS_x,int POS_y);
~Node();
Node operator*(const Move &MOVE_TO) const
{
if (in_image(MOVE_TO.TARGET)) return MOVE_TO.TARGET;
return NULL_NODE;
}
};
class Move
{
public:
Node START,TARGET;
Move(const Node& START,const Node& TARGET);
~Move();
void operator*(const Move &MOVE_TO) const
{
if (in_image(MOVE_TO.TARGET)) this->TARGET=MOVE_TO.TARGET;
return;
}
};
然後是找山體均勻質點的方法,搜索+判定,很簡單:
// -------------------- 搜索方法 -------------------
#define STEP 3
const int MOVE_X[8] = {0,0,STEP,-STEP,-STEP,STEP,-STEP,STEP};
const int MOVE_Y[8] = {STEP,-STEP,0,0,-STEP,STEP,STEP,-STEP};
// 判別是否是山體範圍的均勻質點;
bool is_central(const Node& NODE_NOW)
{
for (size_t i = 0; i < 8; i++)
{
if( !(new Node(NODE_NOW.POS_x+MOVE_X[i],NODE_NOW.POS_y+MOVE_Y[i]))->IN_HILL )
return false;
}
return true;}
// 搜索質點;
Node BFS_central(const Node& NODE_NOW)
{
std::queue<Node> NODE_QUE;
memset(VIS_NODES,0,sizeof(VIS_NODES));
NODE_QUE.push(NODE_NOW);
while (!NODE_QUE.empty())
{
Node NODE_NEXT = NODE_QUE.front();NODE_QUE.pop();
if (is_central(NODE_NEXT)) return NODE_NEXT;
for (size_t i = 0; i < 8; i++)
{
Node *NODE_NEW = new Node(NODE_NOW.POS_x+MOVE_X[i],NODE_NOW.POS_y+MOVE_Y[i]);
if( NODE_NEW->IN_HILL && in_image(*NODE_NEW) && !VIS_NODES[NODE_NEW->POS_x][NODE_NEW->POS_y] ) NODE_QUE.push(&NODE_NEW);
}
}
return NULL_NODE;
}
最後是找下葬地點的方法,搜索+判定,很簡單,搜索方法同上,這裏只寫判定方法:
判定方法具體是怎麼來的呢?其實是按照一定的規則,根據卦象、地支、五行的衝突來判別的;比如<<尋龍訣>>裏的明堂選法:
喪庭在壬,門陌在庚,輿穴不和,故取甲爲門… …
描述的就是每個方向對應一個地支,而地支的組合對應吉凶(刑衝合害);在代碼中我們用hash方法來記錄這些規則;
// ---------------- 水口判定方法 -----------------
typedef std::pair<int,int> Dizhi; // 十二地支的組合對;
std::map<Move*,int> DIRECTION_DIZHI_TAB; // 方向對應十二地支的hash;
std::map<std::pair<int,int>,int> VAL_DIZHI; // 不同地支組合的“刑衝合”取值;
// 利用三點:當前的遍歷點NODE_NOW、山體中心質點、山體出口處 判定NODE_NOW是否是"水口";
bool is_sk_using_dizhi(const Node& NODE_NOW,const Node& NODE_ct,const Node& NODE_out)
{
Move* DIR_ONE = new Move(NODE_NOW,NODE_out);
Move* DIR_TWO = new Move(NODE_NOW,NODE_ct);
Dizhi DIZHI_PAIR(DIRECTION_DIZHI_TAB[DIR_ONE], DIRECTION_DIZHI_TAB[DIR_TWO]);
return (VAL_DIZHI[DIZHI_PAIR] == 1);
}
實現效果
最終將標註圖疊加在最優的圖像位置上,但是現在疊加透明png還有些問題…
題外話
事實上我們可以證明的任意子羣都是正規子羣(雖然並沒有什麼鳥用…);
proof: 對於,其中是的一個子羣,以及有:
所以,證畢;
但是由此我們可以對做商羣,現在考慮是從點發散地到四周的方向變換(就是我們從中心質點搜索最優點的模式),這時這個商羣是有實際意義的(和動態規劃有關),有興趣並思考出答案的讀者可以聯繫我交流;