哈希表初始化:
struct uint64_zarray_entry **clustermap = calloc(nclustermap, sizeof(struct uint64_zarray_entry*));
clustermap 是一個二維指針。哈希表可以理解爲數組+鏈表,這裏的數組大小便是nclustermap,每個數組存儲一個鏈表的起始地址,鏈表中的每個元素類型是uint64_zarray_entry*。
struct uint64_zarray_entry
{
uint64_t id; // 待存儲的邊界點ID
zarray_t *cluster;// 該像素點的聚類信息
struct uint64_zarray_entry *next;// 下一個鏈表節點地址
};
然後對圖像遍歷每行每列,分別檢索對比當前點與"下方、右側、右上方、右下方"的像素值梯度,按梯度進行聚類並進行哈希存儲,具體步驟見下:
if (v0 + v1 == 255) {
if (unionfind_get_set_size(uf, rep1) > 24) {
輪廓檢驗時的條件是:1)兩個鄰接像素值相加爲255;2)鄰接像素所處的連接區域(由聯合查找算法確定)像素點數量大於24。
然後計算待存儲的哈希表鍵值:clusterid,也是聚類ID,滿足上述條件的鄰接像素點ID決定。
uint64_t clusterid; \
/*將兩個點的ID存儲於64位clusterid量中,大的位於前面*/ \
if (rep0 < rep1) \
clusterid = (rep1 << 32) + rep0; \
else \
clusterid = (rep0 << 32) + rep1; \
然後,基於該哈希表的鍵值,也是聚類點ID,計算數組索引:
uint32_t clustermap_bucket = u64hash_2(clusterid) % nclustermap; /*哈希表的數組的編號*/
clustermap_bucket 便爲當前哈希表的一維索引位置。每個像素點聚類後存儲的位置由clustermap_bucket 決定,也就是取模後值相等那些像素點都會存儲在一個鏈表裏,該鏈表的起始地址爲索引號爲clustermap_bucket的數組。clustermap[clustermap_bucket] 。
新建一個哈希表入口變量entry,新建鏈表節點地址便是對應桶/數組中上一個鏈表節點的地址:
struct uint64_zarray_entry *entry = clustermap[clustermap_bucket];/*新建鏈表節點地址便是上一個對應桶中鏈表節點的地址*/
每次開始前會判斷當前的邊界聚類ID是不是和當前對應數組/桶裏存儲的鏈表節點裏存儲的ID是否一致,也就是檢索當前的ID值以前是否存儲過,在程序裏會逐個和之前的clusterid對比,若不等於,則entry繼續指向該數組下的上一個鏈表地址,直到找到對應的聚類ID(此時entry指向上一個節點,下面會進行地址遞增)或者entry指向空(表示找完了都沒有),此時纔會進行下一步:
while (entry && entry->id != clusterid) { \
entry = entry->next;
}
最後,對於每個不爲空的鏈表節點,首先更新當前的鏈表節點地址,也就是疊加鏈表元素大小的地址偏移:
if (mem_pool_loc == mem_chunk_size) { \
mem_pool_loc = 0; \
mem_pool_idx++; \
mem_pools[mem_pool_idx] = calloc(mem_chunk_size, sizeof(struct uint64_zarray_entry)); \
} \
/*鏈表節點地址遞增*/ \
entry = mem_pools[mem_pool_idx] + mem_pool_loc; \
mem_pool_loc++;
將當前的“聚類ID,聚類點信息,當前鏈表節點的起始地址(數組索引)”添加到當前鏈表節點中:
entry->id = clusterid; /*將邊界聚類id賦值,也就是哈希表的鍵值*/ \
entry->cluster = zarray_create(sizeof(struct pt));/*當前聚類信息*/ \
entry->next = clustermap[clustermap_bucket]; /*哈希表的下一個存儲桶或者數組*/ \
然後,哈希表的當前索引位置數組clustermap[clustermap_bucket] 指向entry:
clustermap[clustermap_bucket] = entry; /*當前哈希表桶的入口地址存儲當前聚類的地址*/
同時,不管當前的鏈表節點是否爲空,都更新當前的聚類像素點座標和梯度值,座標按照2倍關係存儲,梯度按照鄰接像素的位置存儲:
struct pt p = { .x = 2*x + dx, .y = 2*y + dy, .gx = dx*((int) v1-v0), .gy = dy*((int) v1-v0)}; \
zarray_add(entry->cluster, &p); \
此時的entry和clustermap[clustermap_bucket]指向的是同一個地址。所以增加的像素點聚類信息也就是增加到了clustermap[clustermap_bucket]指向的鏈表節點中。
最後運行結束後,將clustermap存儲的信息,按順序存儲在clusters中,並返回。clusters便是聚類後的結果,並完全按照哈希表的方式存儲。
for (int i = 0; i < nclustermap; i++) {// 按桶遍歷哈希表,也就是遍歷每個聚類
int start = zarray_size(clusters);
// 遍歷哈希表中每個桶(每個聚類)裏的聚類點信息,並存儲進clusters
for (struct uint64_zarray_entry *entry = clustermap[i]; entry; entry = entry->next) {
struct cluster_hash* cluster_hash = malloc(sizeof(struct cluster_hash));
cluster_hash->hash = u64hash_2(entry->id) % nclustermap;// 哈希表賦值
cluster_hash->id = entry->id;
cluster_hash->data = entry->cluster;
zarray_add(clusters, &cluster_hash);
}
int end = zarray_size(clusters);
// Do a quick bubblesort on the secondary key.
// 對每個桶裏的ID按照冒泡排序
int n = end - start;
for (int j = 0; j < n - 1; j++) {
for (int k = 0; k < n - j - 1; k++) {
struct cluster_hash* hash1;
struct cluster_hash* hash2;
zarray_get(clusters, start + k, &hash1);// 將聚類裏第 start + k個檢索值賦值給hash1
zarray_get(clusters, start + k + 1, &hash2);
if (hash1->id > hash2->id) { // 若ID大於
struct cluster_hash tmp = *hash2; // 將兩個哈希數據位置交換
*hash2 = *hash1;
*hash1 = tmp;
}
}
}
}
// 釋放內存
for (int i = 0; i <= mem_pool_idx; i++) {
free(mem_pools[i]);
}
free(clustermap);
// 返回聚類結果
return clusters;