MATLAB处理OBJ模型

(一)读取、绘制
MATLAB处理OBJ模型_第1张图片
MATLAB处理OBJ模型_第2张图片

(二)模型简化(QEM)
MATLAB处理OBJ模型_第3张图片
MATLAB处理OBJ模型_第4张图片
**
(三)放缩、旋转、移动、保存
MATLAB处理OBJ模型_第5张图片

classdef Mesh < handle
    %MESH  :  3D网格对象
    %继承handel后可以在对象内部修改对象的属性
    properties
        V%  nv*3
        E%线  ne*3
        F%  nf*3
        NV%顶点法线
        NF%平面法线
        file_name%文件名称
        matrix0%操作过程中进行的变换
        
        print%控制 网格更新后是否以绘制出来
    end
    properties (Constant,Hidden)
      m34=[1 0 0 0;0 1 0 0;0 0 1 0]
      voxel_size=0.1;%体素方块大小
    end
    methods
        function n=nv(o)
            n=size(o.V,1);
        end
        function n=ne(o)
            n=size(o.E,1);
        end
        function n=nf(o)
            n=size(o.F,1);
        end
        function out=box(o,type)
            box0=[min(o.V);max(o.V)];
            if nargin==1
                out=box0;
            else
                if type=="max"
                    out=box0(1,:);
                elseif type=="min"
                    out=box0(2,:);
                else%size
                    out=box0(2,:)-box0(1,:);
                end
            end
        end
        function o = Mesh(file_name)
            o.file_name=file_name;
            o.matrix0=eye(4);
            [o.V,o.F] = o.read(file_name);
            %o.mergeVertex();
            o.computeNormal();%计算所有平面的法线
            o.computeEdge();
            o.print=0;%不展示每次网格更新的结果
        end
        function download(o)
            %inv(o.matrix0);
            o.write(o.file_name+"_save",o.V,o.F);
        end
        
        function applyMove(o,in1,in2,in3)
            if nargin==2
                d=in1;
            else
                d=[in1,in2,in3];
            end
            o.applyMatrix([1 0 0 d(1);0 1 0 d(2);0 0 1 d(3);0 0 0 1]);
        end
        function applyRotation(o,in1,in2,in3)
            if nargin==2
                in=in1;
            else
                in=[in1,in2,in3];
            end
            o.applyMatrix(rotx(in(1))*roty(in(2))*rotz(in(3)));
        end
        function applyScale(o,in1,in2,in3)
            if nargin==2
                s=in1;
            else
                s=[in1,in2,in3];
            end
            o.applyMatrix([s(1) 0 0;0 s(2) 0;0 0 s(3)]);
        end
        function applyMatrix(o,mat)
            if length(mat)==3
                mat=[mat;[0 0 0]];
                mat=[mat';[0 0 0 1]]';
            end
            V2=[o.V';ones(o.nv,1)']';%nv*4
            o.V=(o.m34*mat*(V2'))';
            o.matrix0=mat*o.matrix0;%记录进行的变换
            if o.print==1
                o.draw();
            end
        end
        function normal(o)
            %旋转
            voxel=o.voxelization();
            mean0 = mean(voxel);% 样本均值
            Z = voxel-repmat(mean0,length(voxel), 1);%减去均值
            covMat = Z' * Z;% covMat 协方差矩阵 %r*3 3*r
            [mat,~] = eigs(covMat, 3);% V每一列为一个特征向量
            o.applyMatrix(mat');%对齐坐标轴
            
            %翻转
            voxel=o.voxelization();
            mean0 = mean(voxel);% 样本均值
            Z = voxel-repmat(mean0,length(voxel), 1);%减去均值
            o.applyScale((sum(Z.^3)>0)*2-1);%取x^3大于0的方向为正方向
            
            %移动
            voxel=o.voxelization();
            mean0 = mean(voxel);% 样本均值
            o.applyMove(mean0.*-1);
            
            
            %放缩
            %voxel=o.voxelization();
            %d=sum((voxel-mean(voxel)).^2);
            %d=sum(voxel.^2);
            %o.applyScale(ones(1,3)./(d.^0.5));%除以标准差
            
        end
        function reset(o)
            o.applyMatrix(inv(o.matrix0));
        end
        function draw(o)
            clf
            trimesh(o.F, o.V(:,1), o.V(:,2), o.V(:,3),'LineWidth',1,'EdgeColor','k');
            axis equal
            %axis off %隐藏坐标轴
            camlight
            lighting gouraud
            cameratoolbar%创建一个工具栏
            drawnow
            o.print=1;%展示每次网格更新的结果
        end
        function simplify(o,r)
            myQEM=QEM();
            o=myQEM.simplification(o,r);
            if o.print==1
                o.draw();
            end
        end
    end%methods
    methods(Hidden)
        function process(this)
            myQEM=QEM();
            this=myQEM.simplification(this,0.5);
            this.download();
        end
        function computeNormal(o)
            %输入: vertex:nv*3   face:nf*3
            %输出:
            % compute_normal - compute the normal of a triangulation
            %
            %   [normal,normalf] = compute_normal(vertex,face);
            %
            %   normal(i,:) is the normal at vertex i.
            %   normalf(j,:) is the normal at face j.
            
            
            [vertex,face] = check_face_vertex(o.V,o.F);
            %vertex:3*nv   face:3*nf
            
            nface = size(face,2);
            nvert = size(vertex,2);
            
            % unit normals to the faces 单位面法线
            normalf = crossp( vertex(:,face(2,:))-vertex(:,face(1,:)), ...
                vertex(:,face(3,:))-vertex(:,face(1,:)) );
            d = sqrt( sum(normalf.^2,1) );
            d(d<eps)=1;%eps是极小值
            normalf = normalf ./ repmat( d, 3,1 );%面法线单位化
            
            % unit normal to the vertex
            normal = zeros(3,nvert);%顶点法线 normal:3*nv
            for i=1:nface
                f = face(:,i);
                for j=1:3
                    normal(:,f(j)) = normal(:,f(j)) + normalf(:,i);
                end
            end
            % normalize
            d = sqrt( sum(normal.^2,1) ); d(d<eps)=1;
            normal = normal ./ repmat( d, 3,1 );
            
            % enforce that the normal are outward
            v = vertex - repmat(mean(vertex,1), 3,1);
            s = sum( v.*normal, 2 );
            if sum(s>0)<sum(s<0)
                % flip
                normal = -normal;
                normalf = -normalf;
            end
            o.NV=normal';
            o.NF=normalf';
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            function z = crossp(x,y)%x,y可以看做三角形的两个边,z是和它们垂直的方向
                % x and y are (m,3) dimensional
                z = x;
                z(1,:) = x(2,:).*y(3,:) - x(3,:).*y(2,:);
                z(2,:) = x(3,:).*y(1,:) - x(1,:).*y(3,:);
                z(3,:) = x(1,:).*y(2,:) - x(2,:).*y(1,:);
            end
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            function [vertex,face] = check_face_vertex(vertex,face)
                
                % check_face_vertex - check that vertices and faces have the correct size
                %
                %   [vertex,face] = check_face_vertex(vertex,face);
                %
                %   Copyright (c) 2007 Gabriel Peyre
                
                vertex = check_size(vertex,2,4);
                face = check_size(face,3,4);
            end
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            function a = check_size(a,vmin,vmax)
                if isempty(a)
                    return;
                end
                if size(a,1)>size(a,2)
                    a = a';
                end
                if size(a,1)<3 && size(a,2)==3
                    a = a';
                end
                if size(a,1)<=3 && size(a,2)>=3 && sum(abs(a(:,3)))==0
                    % for flat triangles
                    a = a';
                end
                if size(a,1)<vmin ||  size(a,1)>vmax
                    error('face or vertex is not of correct size');
                end
            end
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        end%函数结束
        function computeEdge(o)
            TR = triangulation(o.F,o.V);%进行三角剖分,梳理出所有三角形
            o.E = edges(TR);%返回所有边的顶点索引  ne*2
        end
        function rectifyindex(o)
            %清除为空的顶点
            %RECTIFYINDEX Summary of this function goes here
            
            
            num_of_NaN=zeros(o.nv(),1);
            sum=0;
            for i=1:o.nv()
                if isnan(o.V(i,1)) % 为空 NaN
                    sum=sum+1;
                end
                num_of_NaN(i)=sum;
            end
            
            recF=zeros(o.nf(),3);%三角面个数不变,但是由于顶点个改变,三角面的顶点索引需要修改
            for i=1:o.nf()
                for j=1:3
                    recF(i,j)=o.F(i,j)-num_of_NaN(o.F(i,j));
                end
            end
            
            recV=zeros(o.nv-sum,3);%总个数-为空的个数
            j=1;
            for i=1:o.nv()
                if ~isnan(o.V(i,1))
                    recV(j,:)=o.V(i,:);
                    j=j+1;
                end
            end
            o.V=recV;
            o.F=recF;
        end
        function voxel=voxelization(o)
            box=o.box();
            box_size=box(2,:)-box(1,:);
            step=min(box_size)*o.voxel_size;%体素方块大小
            if step==0
                step=1;
            end
            %box_size_step=ceil(box_size./step);
            voxel=[];%zeros(box_size_step);
            for i=1:o.nf()
                oF=o.F();
                v1=o.V(oF(i,1),:);
                v2=o.V(oF(i,2),:);
                v3=o.V(oF(i,3),:);
                voxel=addF(v1,v2,v3,voxel,box,step);
            end
            voxel=voxel.*o.voxel_size;%+repmat(box(1,:),length(voxel),1);%体素方块大小
            function voxel=addF(v1,v2,v3,voxel,box,step)
                for A=0:0.2:1
                    for B=0:0.2:(1-A)
                        v=v1.*A+v2.*B+v3.*(1-A-B);
                        voxel=addV(v,voxel,box,step);
                    end
                end
            end
            function voxel=addV(v,voxel,box,step)
                v=v-box(1,:);
                v=round(v./step);
                %v=v+ones(size(v));
                if v(1)==0
                    v(1)=1;
                end
                if v(2)==0
                    v(2)=1;
                end
                if v(3)==0
                    v(3)=1;
                end
                voxel=[voxel;[v(1),v(2),v(3)]];
                %voxel(v(1),v(2),v(3))=1;
            end
        end
    end%methods(Hidden)
    methods(Static)
        function [vertex,faces] = read(filename)
            vertex = [];
            faces = [];
            fid = fopen(filename+".obj");%fid是一个大于0的整数
            s = fgetl(fid);
            while ischar(s)
                if ~isempty(s)
                    if strcmp(s(1), 'f')%如果字符串第一个字符为f %face
                        %  F V1 V2 V3 ...
                        %  F V1/VT1/VN1  ...
                        %  F V1//VN1  ...
                        str2=strsplit(s," ");
                        coordinate1=strsplit(cell2mat(str2(2)),"/");
                        if isempty(length(coordinate1)==1)
                            faces(end+1,:) =sscanf(s(3:end), '%d %d %d');
                        else
                            coordinate2=strsplit(cell2mat(str2(3)),"/");%length(str2num("1 "))
                            coordinate3=strsplit(cell2mat(str2(4)),"/");
                            faces(end+1,:) = [
                                str2double(coordinate1(1))
                                str2double(coordinate2(1))
                                str2double(coordinate3(1))
                                ];
                        end
                    elseif strcmp(s(1), 'v')%如果字符串第一个字符为v %vertex
                        vertex(end+1,:) = sscanf(s(3:end), '%f %f %f');
                    end%vertex添加一行、在最后一列  s从第三个字符开始到最后一个
                end
                s = fgetl(fid);%获取下一行
            end
            fclose(fid);
        end
        function write(filename,vertices,faces )
            fid=fopen(filename+".obj",'w');
            fid=arrPrintf(fid,vertices,'v');
            fid=arrPrintf(fid,faces,'f');
            fclose(fid);
            function fid=arrPrintf(fid,arr,head)
                [x,y]=size(arr);
                for i=1:x
                    fprintf(fid,head);
                    for j=1:y
                        fprintf(fid,' %d',arr(i,j));
                    end
                    fprintf(fid,'\n');%每一行回车\n  
                end
            end
        end
    end%methods(Static)
    methods(Static,Hidden)%用于测试的方法
        function test()
            mesh=Mesh("man_sim2");
            myQEM=QEM();
            mesh=myQEM.simplification(mesh,0.5);
            mesh.download();
        end
        function test2()
            mesh=Mesh("mesh2");
            mesh.box();
            mesh.applyMatrix([
                1 0 0 ;
                0 0 1 ;
                0 1 0 
                ]);
            voxel=mesh.voxelization();
            size(voxel);
            %sum(voxel,"all")
            mesh.normal();
            mesh.draw();
            %mesh.download();
        end
        function test3()
            %untitled
            Mesh.read("untitled");
        end
    end%methods(Static)
end%class

QEM模型简化算法
参考:https://lafengxiaoyu.blog.csdn.net/article/details/72812681?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.baidujs&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.baidujs

原始算法有一个BUG,每次更新代价矩阵时,如果需要更新的点只有一个的时候运行会出错,添加一句“s=permute(s, [2,1,3])”就可以解决这个问题。

classdef QEM < handle
    properties
        %mesh
        QVex %4*4*nv  顶点的矩阵
        QEdge%4*4*ne  边的矩阵
        cost % ne*3 
        v    %4*3*ne  顶点坐标
    end
    methods
        function o= QEM()
        end
        function mesh=simplification(o,mesh,percent )
            o.pretreatment(mesh);
            %开始坍塌%坍塌后的空洞推测是由于“多网格”/“重合点”引起的
            for iii = 1:(1-percent)*mesh.nv()%每次删除一个顶点(一条边/一个三角面)
                [min_cost, vidx] = min(o.cost,[],2);%返回包含每一行的最小值的列向量
                % min_cost:ne*1   vidx:ne*1
                
                [~, k] = min(min_cost);%获取代价最小的边序号
                mesh=o.deleteEdge(k,mesh, vidx);
                
            end
            mesh.rectifyindex();
            
        end%simplification
        function pretreatment(o,mesh)
            N=mesh.NF;
            nv = mesh.nv(); % 顶点个数
            %计算平面方程
            p=getPlane(mesh.V,mesh.F,N);
            function p=getPlane(V,F,N)%所有的平面,n*4
                %p = [N, -sum(N .* V(F(:,1),:), 2)];
                v0=V(F(:,1),:);
                d=sum(v0.*N,2);
                p=[N,-d];%每一行对应一个平面
            end
            
            %计算三角面的矩阵
            QFace = getQFace(p);%面数个4*4的矩阵,size为4*4*n
            function QFace=getQFace(p)
                %bsxfun(@times, permute(p, [2,3,1]), permute(p, [3,2,1]))
                %p的维度是 n*4=n*4*1
                p1=permute(p, [2,3,1]);%4*1*n
                p2=permute(p, [3,2,1]);%1*4*n
                QFace=bsxfun(@times,p1,p2);%4*4*n
            end
            
            %1.计算顶点的矩阵Q
            o.QVex=getQ(mesh.F,QFace,nv);%4*4*nv
            function Q=getQ(F,QFace,nv)
                Q = zeros(4,4,nv);%每个顶点都对应一个4*4的矩阵
                nf = size(F,1);%三角面个数
                for i = 1:nf%遍历三角面
                    for j = 1:3%遍历三角面的顶点
                        v_indx=F(i,j);%获取三角面上的顶点序号
                        Q(:,:,v_indx) = Q(:,:,v_indx) + QFace(:,:,i);%顶点的矩阵等于所有所处三角面的矩阵和
                    end
                end
            end
            
            %2.计算每个边矩阵
            %E=getE(mesh.F,mesh.V);%获取所有边
            ne = size(mesh.E,1);
            o.QEdge = getQEdge(o.QVex,mesh.E);%边矩阵的合理性在于两个端点折叠后的位置相同,计算出所有的边矩阵
            %QEdge:4*4*ne
            function QEdge=getQEdge(Q,E)% compute Q1+Q2 for each pair
                %QEdge = Q(:,:,E(:,1)) + Q(:,:,E(:,2))
                e1=E(:,1);% ne*2 -> ne*1
                e2=E(:,2);
                QEdge = Q(:,:,e1) + Q(:,:,e2);% Q:4*4*nv -> QEdge:4*4*ne
            end
            
            %3.计算每个边的代价
            [o.cost,o.v]=getcost(o,mesh.V,mesh.E,ne,o.QEdge);
            %cost:ne*3 v:4*3*ne
            function [cost,v]=getcost(o,V,E,ne,QEdge)% a simple scheme: select either v1, v2 or (v1+v2)/2
                %v:4*3*ne (坐标+1)*3个点)*(边数)
                v=getv(V,E,ne);%4*3*ne
                function v=getv(V,E,ne)
                    v1 = getv_4(V,E,ne,1);%4*1*ne
                    v2 = getv_4(V,E,ne,2);
                    function v_4=getv_4(V,E,ne,col)
                        %v1 = permute([V(E(:,1),:),ones(ne,1)], [2,3,1]);%获取边的另一个端点位置
                        %v2 = permute([V(E(:,2),:),ones(ne,1)], [2,3,1]);%获取边的另一个端点位置
                        vertex_index=E(:,col);%边的第一列数据 ne*1
                        vertex_pos=V(vertex_index,:);% V:nv*3 -> vertex_pos:ne*3
                        v_4 = permute([vertex_pos,ones(ne,1)], [2,3,1]);%ne*4*1 ->4*1*ne %获取边的一个端点位置
                    end
                    vm = 0.5 .* (v1 + v2);%获取边的中点位置
                    v = [v1, v2, vm]; % 4*1*ne -> 4*3*ne
                end
                
                cost = zeros(ne,3);%用于记录每条边的代价
                cost(:,1)=o.get_costi(v(:,1,:),QEdge);% ne*1
                cost(:,2)=o.get_costi(v(:,2,:),QEdge);
                cost(:,3)=o.get_costi(v(:,3,:),QEdge);
                
                
                %{
                for i=1:ne
                    if i==1
                        %display(cost(i,1))
                    end
                    if V(E(i,1),1)>0 %如果x>0 代价加100
                        cost(i,1)=cost(i,1)+100;
                        cost(i,2)=cost(i,2)+100;
                        cost(i,3)=cost(i,3)+100;
                    end
                end
                %}
                
            end
            
        end%pretreatment
        function mesh=deleteEdge(o,k,mesh, vidx)
            %k是待删除的边的序号
            e = mesh.E(k,:);%获取边对应的两个顶点
            
            % update position for v1
            mesh.V(e(1),:) = o.v(1:3, vidx(k), k)';%一个顶点坍塌到指定位置
            mesh.V(e(2),:) = NaN;%删除另一个顶点
            
            % update Q for v1  %更新代价矩阵,这里的代价之后似乎重新计算了
            o.QVex(:,:,e(1)) = o.QVex(:,:,e(1)) + o.QVex(:,:,e(2));%e(1)的代价为之前两个点的代价之和
            o.QVex(:,:,e(2)) = NaN;%e(2)的代价为空
            
            %更新三角面
            mesh.F(mesh.F == e(2)) = e(1);%e1、e2都是具体数值 %三角面中e2的索引现在都指向e1
            f_remove = sum(diff(sort(mesh.F,2),[],2) == 0, 2) > 0;%如果三角面中有两个相同的点就应当移除
            mesh.F(f_remove,:) = [];%需要移除的平面置为空
            
            %删除去除的边和与该边相关的信息 collapse and delete edge and related edge information
            mesh.E(mesh.E == e(2)) = e(1);%边中e2的索引现在都指向e1
            mesh.E(k,:) = [];%k是代价最小的边序号,置为空
            o.cost(k,:) = [];%修改边的代价信息
            o.QEdge(:,:,k) = [];%删除边对应的矩阵
            o.v(:,:,k) = [];%v的每行对应一条边
            
            %删除重复的边和与该边相关的信息 delete duplicate edge and related edge information
            [mesh.E,ia] = unique(sort(mesh.E,2), 'rows'); %E:ne*2 获取独一的行(边)
            o.cost = o.cost(ia,:);
            o.QEdge = o.QEdge(:,:,ia);%QEdge:4*4*ne
            o.v = o.v(:,:,ia);
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            % pairs involving v1
            pair = sum(mesh.E == e(1), 2) > 0;%与e1相关的边的序号
            npair = sum(pair);%与e1相关的边的个数
            
            
            % updata edge information
            o.QEdge(:,:,pair) = o.QVex(:,:,mesh.E(pair,1)) + o.QVex(:,:,mesh.E(pair,2));
            %QEdge:4*4*ne  pair:n*1
            
            
            pair_v1 = permute([mesh.V(mesh.E(pair,1),:),ones(npair,1)], [2,3,1]);
            pair_v2 = permute([mesh.V(mesh.E(pair,2),:),ones(npair,1)], [2,3,1]);
            %pair_v2:3*1*n       V(E(pair,2),:))--n*3
            %pair_v2:3*1*1       V(E(pair,2),:))--1*3
            pair_vm = 0.5 .* (pair_v1 + pair_v2);
            o.v(:,:,pair) = [pair_v1, pair_v2, pair_vm];
            
            
            %更新所有与e1相关的边的代价
            o.cost(pair,1) =o.get_costi(pair_v1,o.QEdge(:,:,pair));
            o.cost(pair,2) =o.get_costi(pair_v2,o.QEdge(:,:,pair));
            o.cost(pair,3) =o.get_costi(pair_vm,o.QEdge(:,:,pair));
            % cost(pair,1) =sum(squeeze(sum(bsxfun(@times,pair_v1,QEdge(:,:,pair)),1)).*squeeze(pair_v1),1)';
        end%deleteEdge
    end% methods
    methods(Static)
        function costi=get_costi(vi,QEdge)
            costi=QEM.get_costi1(vi,QEdge);
        end
        function costi=get_costi1(vi,QEdge)
            %输入  vi:4*1*ne    QEdge:4*4*ne
            %输出  costi:ne*1
            %统一使用边矩阵?,感觉不是很合理
            bsx=bsxfun(@times,QEdge,vi); %{QEdge:4*4*ne   vi:4*1*ne } -> 4*4*ne
            
            s=sum(bsx,1);               % 4*4*ne -> 1*4*ne
            s=permute(s, [2,1,3]);%!!!!!!!!!!!!!!!!!解决了BUG
            costi=sum(squeeze(s).*squeeze(vi),1)';
            %           s:1*4*ne  vi:4*1*ne
            % ne*1 = {  1*4*ne ,  4*1*ne  }'
            %坐标z>=0的点删除代价加大
            
            for i=1:size(vi,3) %  vi:4*1*ne
                if vi(3,1,i)>=0  %z>=-1
                    costi(i)=costi(i)*10000000;%最前面
                elseif vi(3,1,i)>=-1  %z>=0
                    costi(i)=costi(i)*1000;%中间
                end
            end
        end
        function costi=get_costi0(vi,QEdge)
            %输入  vi:4*1*ne    QEdge:4*4*ne
            %输出  costi:ne*1
            %统一使用边矩阵?,感觉不是很合理
            bsx=bsxfun(@times,QEdge,vi); %{QEdge:4*4*ne   vi:4*1*ne } -> 4*4*ne
            
            s=sum(bsx,1);               % 4*4*ne -> 1*4*ne
            s=permute(s, [2,1,3]);%!!!!!!!!!!!!!!!!!解决了BUG
            costi=sum(squeeze(s).*squeeze(vi),1)';
            %           s:1*4*ne  vi:4*1*ne
            % ne*1 = {  1*4*ne ,  4*1*ne  }'
        end
    end%methods(Static)
end%class


你可能感兴趣的:(文件处理)