रे कास्टिंग विजुअल सर्च (RCVS)। ज्यामिति में समान 3 डी मॉडल खोजने के लिए सरल और तेज एल्गोरिदम



मेरे लिए, ये दो मॉडल बहुत समान हैं, लेकिन उनके पास स्पष्ट विशेषताएं नहीं हैं जिनके द्वारा उनकी समानता को मापा जा सकता है। इन मॉडलों में अलग-अलग संख्याएं हैं, किनारों और बहुभुज, वे अलग-अलग आकार के हैं, इसके अलावा उन्हें अंतरिक्ष में अलग-अलग घुमाया जाता है, और दोनों में एक ही परिवर्तन होता है (स्थिति = [0,0,0], रेडियन में रोटेशन = [0,0, अलग) 0], स्केल = [1,1,1])। उनकी समानता का निर्धारण कैसे करें?

यदि ये मॉडल अंतरिक्ष में समान रूप से उन्मुख थे, तो उन्हें बाउंडिंग बॉक्स के समग्र आकार में घटाया जा सकता है, स्थानीय निर्देशांक की उत्पत्ति को इसके केंद्र में स्थानांतरित कर सकते हैं, एक voxel दृश्य का नेतृत्व कर सकते हैं और voxels की सूची की तुलना कर सकते हैं।


बंदर और उनके स्वर।

यदि ये मॉडल समन्वित अक्षों के साथ मनमाने ढंग से उन्मुख थे, तो उन्हें बाउंडिंग बॉक्स के समग्र आकार में भी कम किया जा सकता है, स्थानीय निर्देशांक की उत्पत्ति को इसके केंद्र में स्थानांतरित कर सकते हैं, और फिर उन्हें छह पक्षों पर प्रस्तुत कर सकते हैं और आपस में छवियों के सेट की तुलना कर सकते हैं।


सतह वक्रता के रेंडर।

रोटेशन के आक्रमण को प्राप्त करने के लिए, मैंने एक गोलाकार समन्वय प्रणाली को चालू करने का निर्णय लिया।

आर=एक्स2+y2+z2

पहले तो मैंने उत्पत्ति से स्वरों की दूरियों को गिना, आदेशित सूचियों में दूरियों को एकत्र किया और इन सूचियों की तुलना की। इससे अच्छे परिणाम मिले, लेकिन स्वर को पर्याप्त संकल्प में बदलने और बड़ी सूचियों की तुलना करने में लंबा समय लगा।

RCVS का संक्षिप्त विवरण


वोकलाइज़ेशन और रेंडरिंग के बजाय, मेरी विधि रे कास्टिंग का उपयोग करती है। मॉडल को आइकोसाहेड्रल गोलाकार पॉलीहेड्रॉन के अंदर रखा गया है, जिसमें से पॉलीगॉन किरणें फेंकी जाती हैं और मॉडल की सतह तक उनकी लंबाई (पथ) दर्ज की जाती है। इन रास्तों की सूचियों को एक दूसरे के साथ क्रमबद्ध और तुलना किया जाता है। सॉर्टिंग घुमावों को समाप्त कर देता है, क्योंकि किरणों के पथ के ज्यामिति मॉडल में समान या करीबी त्रुटि के भीतर मेल खाती है, लेकिन क्रम में भिन्न होती है।


क्रमशः 80 और 1280 किरणें।

विभिन्न तरीकों से सुज़ैन के बंदर मॉडल की तुलना के परिणाम


Suzanne_lo
                          Suzanne_lo | 1             
                 Suzanne_lo_rot_rand | 0.9878376875939173
                   Suzanne_lo_rot_90 | 0.9800766750109137
                 Suzanne_hi_rot_rand | 0.9761078629585936
                          Suzanne_hi | 0.9730722945028376
                   Suzanne_hi_rot_90 | 0.9680325422930756
                               Skull | 0.7697333228143514
             Ceramic_Teapot_rot_rand | 0.6634783235048608
                      Ceramic_Teapot | 0.6496529954470333
                         Grenade_MK2 | 0.5275616759076022

आरसीवीएस का विस्तृत विवरण


दृष्टांत
















3D मॉडल की तुलना करने से पहले (बाद में इसे ऑब्जेक्ट के रूप में संदर्भित किया जाता है), हर एक तैयारी के चरण से गुजरता है (मैंने ब्लेंडर में पायथन में इस भाग को लागू किया):

  1. ऑब्जेक्ट के घटकों के स्थानीय निर्देशांक की उत्पत्ति वॉल्यूम के द्रव्यमान के केंद्र में सेट की गई है।
  2. .
  3. .
  4. ( ) , . , : 80, 320, 1280, 5120, 22480. 1280.
  5. , .
  6. .
  7. रेजोस्फियर के प्रत्येक बहुभुज से एक बार फिर से किरणें भेजी जाती हैं और उनका मार्ग तब तक बचाया जाता है जब तक कि वे वस्तु की सतह से नहीं मिलतीं, जिसके मानदंडों को ऑब्जेक्ट के अंदर निर्देशित किया जाता है। (यह आंतरिक ज्यामिति को ठीक करता है और / या पिछली किरणों के अंधे धब्बों को भरता है)
  8. विपरीत पॉलीगनों से भेजी गई किरणों के रास्तों को सूचियों में व्यवस्थित किया जाता है, जहां पहले चरण 5 से पथ और दूसरे चरण 2 से हैं। प्रत्येक सूची को चरण 5 से सबसे छोटे पथ मान द्वारा क्रमबद्ध किया गया है ताकि चरण 5 और 7 से मार्ग जोड़े में एक दूसरे से मेल खाते हों।
  9. सभी रास्तों की सूचियों की सूची पहले मूल्य से क्रमबद्ध है। इस सूची में 4 कॉलम और पंक्तियों की संख्या के समान संख्या में बहुभुज की आधी संख्या है।
  10. किरण पथों की सूची एक फ़ाइल में लिखी जाती है।

किरण पथों की सूची कुछ इस प्रकार है:

0.00271218840585662  0.2112752957162676 0.00271218840585662  0.2112752957162676
0.004740002405006037 0.210980921765861  0.004740002405006037 0.2109809188911709
0.00660892842734402  0.2066613370130234 0.00660892842734402  0.2066613370130234
0.2692299271137439   0.2725085782639611 0.2692299271137439   0.2725085782639611
0.2705489991382905   0.2707842753523402 0.270548999138295    0.2707843056356914
0.2709498404192674   0.271036677664217  0.2709498404192674   0.271036642944409

जब सभी वस्तुओं को किरण पथों की तैयार सूची मिली, तो उनकी तुलना की जा सकती है (मैंने इस हिस्से को रस्ट में लिखा है):

  1. . 0.01, 1%.
  2. .
  3. , , . , . , .
  4. , . .
  5. ( ), .

, RCVS


एल्गोरिथ्म उन वस्तुओं की खोज के लिए बहुत अच्छा काम करता है जो ज्यामिति के करीब हैं, उदाहरण के लिए, एक ही मॉडल के उच्च और निम्न-बहुभुज संस्करण, और कभी-कभी यह समान वस्तुओं के साथ अच्छी तरह से मुकाबला करता है, उदाहरण के लिए, विभिन्न आकृतियों के teapots या गिटार के साथ। हालाँकि, मॉडल की संख्या जो किरण पथों की समान सूची के अनुरूप होगी, लगभग अनुमान लगाया जा सकता हैn!(n2)!, एक महत्वपूर्ण संभावना है कि ज्यामिति में नेत्रहीन रूप से भिन्न वस्तुओं का 0.5 - 0.9 की सीमा में संयोग का एक खाता होगा। परीक्षणों के दौरान, यह केले के साथ हुआ, जो 0.7 द्वारा हवाई जहाज और लोगों के साथ अलग-अलग पोज़ में होता है, जो बदले में "प्रभावशाली" 0.85 बाघों और कुत्तों के साथ मेल खाता है।

अधिक सटीक खोज के लिए, वस्तुओं के आकार और / या रोटेशन पर विचार करें।

सूत्रों का कहना है।


ब्लेंडर में विरोधाभासी बहुभुज का शब्दकोश बनाने के लिए पायथन कोड
import bpy
import os

name = bpy.context.active_object.data.name

polys = bpy.data.meshes[name].polygons
print("-----")
length = len(polys)

x = 0
p = 0
dict = []
for i in range(0, length):
    print(i, "/", length)
    for l in range(i, length):
        norm1 = polys[i].normal
        norm2 = polys[l].normal
        if norm1 == -norm2:
            dict.append(str(i)+", "+str(l)) 
            p += 1  


print("Polys", p)
basedir = os.path.dirname(bpy.data.filepath) + "/ico_dicts/"  
if not os.path.exists(basedir):
    os.makedirs(basedir)
file = open(basedir + "/" + str(length) + ".csv","w")
for poly in dict:      
    file.write(poly + "\n")
file.close()
print("----ok----")


ब्लेंडर में किरण पथों की सूची बनाने और वस्तुओं को तैयार करने के लिए पायथन कोड।
import bpy
import os
from math import sqrt
import csv


#Icosphere properties
#icosubd:
#5 -> 5120 X 2 rays
#4 -> 1280 X 2 rays
#3 ->  320 X 2 rays
#2 ->   80 X 2 rays
icosubd = 4
size = 1280 #name of directory
radius = 1




def icoRC(icosubd, rad, raydist, normals, target):
    icorad = rad
    bpy.ops.mesh.primitive_ico_sphere_add(radius=icorad, subdivisions=icosubd, enter_editmode=True, location=(0, 0, 0))
    bpy.ops.mesh.normals_make_consistent(inside=normals)
    bpy.ops.object.editmode_toggle()
    

    ico = bpy.context.active_object
    mesh = bpy.data.meshes[ico.data.name]
    rays = []
    size = len(mesh.polygons)
    for poly in mesh.polygons:
        normal = poly.normal
        start = poly.center
        
        ray = target.ray_cast(start, normal, distance=raydist)
      
        if ray[0] == True:
            length = sqrt((start[0]-ray[1][0]) ** 2 + (start[1]-ray[1][1]) ** 2 + (start[2]-ray[1][2]) ** 2)
            rays.append(length / (radius *2))
        else:
            rays.append(-1)
            
        
    result = []
    #Combine opposite rays using CSV dicts
    dictdir = os.path.dirname(bpy.data.filepath) + "/ico_dicts/"
    with open(dictdir+str(size)+'.csv', newline='') as csvfile:
        ico_dict_file = csv.reader(csvfile, delimiter=',')
        for row in ico_dict_file:
            result.append([rays[int(row[0])], rays[int(row[1])]])
            
            
    bpy.ops.object.select_all(action='DESELECT')
    bpy.data.objects[ico.name].select_set(True) 
    bpy.ops.object.delete() 
    
    return result
   
   
    
    
#Batch preparation of objects by scale and origin 
def batch_prepare():   
    for obj in bpy.context.selected_objects:
        bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_VOLUME', center='MEDIAN')
        max_dist = 0
        bb = bpy.data.objects[obj.name].bound_box
        
        for vert in obj.data.vertices:
            dist = vert.co[0]**2 + vert.co[1]**2 + vert.co[2]**2
            if dist > max_dist:
                max_dist = dist
        
        obj.scale *= (1 / sqrt(max_dist))



def write_rays(dir):  
    for obj in bpy.context.selected_objects:
        bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
        
        #Cast rays on object with normals turned inside   
        bpy.context.view_layer.objects.active  = obj
        bpy.ops.object.editmode_toggle()
        bpy.ops.mesh.normals_make_consistent(inside=True)
        bpy.ops.object.editmode_toggle()
        result1 = icoRC(icosubd, radius, radius*2, True, obj)
        
        #Cast rays on object with normals turned outside 
        bpy.context.view_layer.objects.active  = obj
        bpy.ops.object.editmode_toggle()
        bpy.ops.mesh.normals_make_consistent(inside=False)
        bpy.ops.object.editmode_toggle()
        result2 = icoRC(icosubd, radius, radius*2, True, obj)
        
        final = []
        #Sort sub-lists and append them to main list
        for i in range(0, len(result1)):
            if result2[i][0] < result2[i][1]:
                final.append([result2[i][0], result2[i][1], result1[i][0], result1[i][1]])
            else:
                final.append([result2[i][1], result2[i][0], result1[i][1], result1[i][0]])
        
        
        
     
        basedir = os.path.dirname(bpy.data.filepath) + "/rays/" + str(size) + "/" 
        if not os.path.exists(basedir):
            os.makedirs(basedir)
        file = open(basedir + "/" + obj.name,"w")
        
        final.sort(key=lambda x: x[0]) #Sort list by first values in sub-lists
     
        #Writing to file   
        for ray in final:
            ray_str = ""
            for dist in ray:   
                ray_str += str(dist) + " "
            file.write(ray_str + "\n")

        file.close()
        
    
    
batch_prepare()
write_rays(size)


सूचियों की तुलना के लिए जंग कोड।
use rayon::prelude::*;
use std::collections::HashSet;
use std::fs;
use std::io::{BufRead, BufReader, Error, Read};

//Creates Vector from file
fn read<R: Read>(io: R) -> Result<Vec<Vec<f64>>, Error> {
    let br = BufReader::new(io);
    br.lines()
        .map(|line| {
            line.and_then(|v| {
                Ok(v.split_whitespace()
                    .map(|x| match x.trim().parse::<f64>() {
                        Ok(value) => value,
                        Err(_) => -1.0,
                    })
                    .collect::<Vec<f64>>())
            })
        })
        .collect()
}

//Compares two objects
fn compare(one: &Vec<Vec<f64>>, two: &Vec<Vec<f64>>, error: f64) -> f64 {
    let length = one.len();
    let mut count: f64 = 0.0;
    let mut seen: HashSet<usize> = HashSet::new();

    for orig in one {
        let size = orig.len();

        for i in 0..length {
            if seen.contains(&i) {
                continue;
            } else {
                let targ = &two[i];

                if (0..size).map(|x| (orig[x] - targ[x]).abs()).sum::<f64>() > error * size as f64 {
                    continue;
                }

                let err_sum = (0..size)
                    .map(|x| 1.0 - (orig[x] - targ[x]).abs())
                    .sum::<f64>();

                count += err_sum / size as f64;
                seen.insert(i);
                break;
            }
        }
    }
    count / length as f64
}

//Compares one object with others on different threads
fn one_with_others(
    obj: &(String, Vec<Vec<f64>>),
    dict: &Vec<(String, Vec<Vec<f64>>)>,
) -> Vec<(usize, f64)> {
    let length = dict.len();

    (0..length)
        .into_par_iter()
        .map(|x| {
            println!("Puny human is instructed to wait.. {:}/{:}", x + 1, length);
            (x, compare(&obj.1, &dict[x].1, 0.01))
        })
        .collect()
}

fn main() {
    let ray = 1280; //name of directory
    let obj_cur = "Suzanne_lo"; //name of object

    let mut objects = Vec::<(String, Vec<Vec<f64>>)>::new();
    let path_rays = format!("./rays/{:}/", ray);
    let paths = fs::read_dir(path_rays).unwrap();

    let obj_path = format!("./rays/{:}/{:}", ray, obj_cur);
    let obj = (
        obj_cur.to_string(),
        read(fs::File::open(obj_path).unwrap()).unwrap(),
    );

    for path in paths {
        let dir = &path.unwrap().path();
        let name = dir.to_str().unwrap().split("/").last().unwrap();

        objects.push((
            name.to_string(),
            read(fs::File::open(dir).unwrap()).unwrap(),
        ));
    }

    let mut result = one_with_others(&obj, &objects);
    result.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
    print!("{}\n", "_".repeat(150));
    println!("{:}", obj.0);
    for line in result {
        println!(
            "{:>36} |{:<100}| {:<14}",
            objects[line.0].0,
            "▮".repeat((line.1 * 100.0).round() as usize),
            line.1
        );
    }
}



स्रोत और ब्लेंडर परियोजना के लिए एक परीक्षण दृश्य के साथ लिंक।

GitHub से लिंक करें।


All Articles