"寻龙点穴:阴宅坟山选址真鉴"的计算机视觉算法+python实现


寻龙点穴是阴宅风水的重中之中,指的是根据山脉水势寻找棺木埋葬的最优点;因此可以形式化地描述为在一个图片中寻找某种规则约束下的最优点的问题(约束下空间最优点搜索);我们采用代数和计算机视觉结合的方法来尝试实现"寻龙点穴";

当然,由于水平有限,以及古籍和现代算法描述的差异,不能完全符合原生方法的精髓;当前没有放出全部代码,因为个别部分还在调试(谁能帮我解决如何使用python叠加透明png图片…)

在这里插入图片描述


 TO DO: 
          入    山    尋    水    口
          登    穴    看    明    堂
                                    
                                    -----------------  <<  尋龍點穴 . 陰宅墳山選址祕籍 >>

数据集的制作

我收集了各异的100张山体图片,制作成 256 × 256 256 \times 256 256×256的尺寸,如果有需要,可以下载:

链接: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;        

在图像中搜索

在这里插入图片描述

首先考虑我们把图像划分为离散的区块(比如 256 × 256 256 \times 256 256×256的尺寸图片划分为 16 × 16 16 \times 16 16×16的区块分布),然后我们在这些区块里搜寻目标的明堂节点 T m d T_{md} Tmd水口节点 T s k T_{sk} Tsk并最终在图片上标注出来;

现在定义一个点到点的转移符号 t = ( P i , P j ) t = (P_i,P_j) t=(Pi,Pj),并且在转移的方式上定义它们的复合操作:

t 1 + t 2 = ( P i , P j ) + ( P k , P l ) = ( P i , P l ) = t 3 t_1+t_2 = (P_i,P_j)+(P_k,P_l) =(P_i,P_l)=t_3 t1+t2=(Pi,Pj)+(Pk,Pl)=(Pi,Pl)=t3

注意这里 P j P_j Pj可以等于 P k P_k Pk;

那么事实上我们可以定义一个空间点转移群记作 G = ( t i , + ) G=(t_i,+) G=(ti,+),其中 t i t_i ti就是在节点之间上下左右以不定的步长移动的动作,而 + + +就是这些动作的复合,并且约定 e = ( p i , p i ) , ∀ p i ∈ G e=(p_i,p_i),\forall p_i \in G e=(pi,pi),piG(幺元是唯一的);

现在算法分两大步:

  • 搜索山体的均匀质点:从初始节点开始找一个尽量落在山体质心的区域;

  • 搜索最优的下葬区域:从山体质心开始找一个符合寻龙方法规则的最优点;

因此其实两步都可以抽象为图搜索+规则判定,我们采用广度优先搜索(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;
}

最后是找下葬地点的方法,搜索+判定,很简单,搜索方法同上,这里只写判定方法:

判定方法具体是怎么来的呢?其实是按照一定的规则,根据卦象、地支、五行的冲突来判别的;比如<<寻龙诀>>里的明堂选法:

丧庭在壬,门陌在庚,舆穴不和,故取甲为门… …

描述的就是每个方向 t i t_i ti对应一个地支 z i z_i zi,而地支的组合 ( z i , z j ) (z_i,z_j) (zi,zj)对应吉凶(刑冲合害);在代码中我们用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还有些问题…

在这里插入图片描述


题外话

事实上我们可以证明 G = ( t i , + ) G=(t_i,+) G=(ti,+)的任意子群都是正规子群(虽然并没有什么鸟用…);

proof: 对于 ∀ t ∈ G ′ \forall t\in G' tG,其中 G ′ G' G G G G的一个子群,以及 ∀ a ∈ G \forall a\in G aG有:

a t a − 1 = ( P i , P j ) + ( P k , P l ) + ( P j , P i ) = ( P i , P i ) = e ∈ G ata^{-1} = (P_i,P_j)+(P_k,P_l)+(P_j,P_i)=(P_i,P_i)=e\in G ata1=(Pi,Pj)+(Pk,Pl)+(Pj,Pi)=(Pi,Pi)=eG

所以 G ′ ◃ G G' \triangleleft G GG,证毕; □ \square

但是由此我们可以对 G ′ G' G做商群 G / G ′ G/G' G/G,现在考虑 G ′ G' G是从点 P i P_i Pi发散地到四周的方向变换(就是我们从中心质点搜索最优点的模式),这时这个商群是有实际意义的(和动态规划有关),有兴趣并思考出答案的读者可以联系我交流;

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐