Use matlab to convert bitmap to SVG vector


Conventional statement: I have no relevant engineering application experience, and I only write this blog purely because I am interested in related algorithms. So if there are mistakes, welcome to correct them in the comment area, thank you very much. This article mainly focuses on the implementation of the algorithm. I have no experience in practical applications and other issues, so I will not involve it anymore.

0 Preface

There are many ways to convert a bitmap to a vector image, some use line feature conversion, and some use color feature conversion. According to the purpose, each has advantages and disadvantages.
Line feature conversion is mainly based on the trend of the line, using lines such as straight lines or Bezier curves to fit the original bitmap, and is mostly used for bitmap pictures of wireframe text. Based on color features, it focuses on the color distribution of the bitmap, and uses polygons or curves to fit the image composed of each color block.

The main algorithm in this paper is to use color features for conversion.
The idea of ​​the algorithm is: 1 Median filtering of pictures to reduce noise. 2 Extract the color of the picture and reduce the color of the graphic. 3 Divide the layers represented by different colors into several independent polygons. 4 Convert these polygons to vector polygons and give them colors.

1 Algorithm idea

1.1 Read pictures

Use matlab's own picture pepper
insert image description here

1.2 Median filtering

Filter out noise and remove some sporadic color distributions, so that the generated colors can represent the entire graphic.
Therefore, a median filter is used. The filtering effect is as follows:
insert image description here

1.3 Median filtering

Use the rgb2ind function to reduce the number of colors in an image to a specified number. At this time, an X index matrix and a map map in RGB format are generated. Each value in X represents a color, and the specific color can be found in the map according to the index. Makes the number and color of polygons to be a finite value afterwards.
insert image description here

1.4 Remove isolated pixels

It can be seen that there are many noise-shaped pixel blocks in the background of 1.3, such as the flannelette above the picture and the flannelette below the picture.
These pixel blocks are due to the color being in between, resulting in an extremely fragmented distribution.

Therefore, to reduce the polygon count, we find these isolated pixels such that its color is equal to the color of surrounding pixels.
The method is to traverse each pixel, if the pixel is the same as less than 2 of the surrounding 8 pixels, it means that the pixel is relatively isolated and should be assimilated into the surrounding pixels.

The result after removal is as follows. You can see that the color of the curtain has changed to several full colors. Some noise-like colors on the fruit are also removed.
insert image description here

1.5 Extracting individual colors

The new X index obtained in 1.4 is used for separate color extraction. Taking BW=(X==1) as an example, an image with only 0 and 1 is extracted at this time.
According to the comparison of the results in 1.4, it can be seen that the dark red graphics have been extracted.

This can also be seen as image segmentation.

insert image description here

1.6 Find all the connective bodies in the binary image

Divide the above graphic area, find every connected area, and record the edge.
Of course, matlab has special functions to deal with this problem, respectively for bwconncomp to segment, and boundary to extract edges.

However, when the boundary extracts the edge, it can only extract the convex hull, and the holes and depressions in the middle are easy to ignore.

So in order to minimize this effect, we draw the graphics with large area first, and draw the graphics with small area later. In this way, if a hole is not counted as an edge, first draw the large image as the background, and then when the hole with a small area is drawn later, it will be displayed above the layer of the large image without being affected.

Afterwards, the edge extracted by the boundary is the polygon required for the final drawing of the vector diagram.

insert image description here

1.7 Dilate each extracted polygon

Afterwards, polygons are drawn sequentially according to the area size of the polygons, and filled with colors.

It can be found that there will be gaps between each polygon. This is because when the polygon is extracted, it is extracted according to the coordinates at the center of the pixel point, which differs by about 0.5 pixels from the boundary of the actual bitmap pixel. Adjacent polygons each differ by 0.5 pixels, resulting in a gap about 1 pixel or so in the end.

As shown in the figure below:
insert image description here
Therefore, each polygon needs to be expanded, and the distance of about 1 pixel should be expanded uniformly around without changing the shape of the graphic.

The principle is to expand based on the vertices of polygons, as shown in the figure below:
insert image description here
label the graphics counterclockwise, and the blue vector is the vector pointing to the next point along the direction of the label. The red vector is the vector translated from the previous blue vector, that is, the red vector 2 and so on are obtained by the translation of the blue vector 1, and the red vector 3 and so on are obtained by the translation of the blue vector 2.

We stipulate that the direction of expansion is to take one step towards the direction of the red vector, and then one step towards the direction opposite to the direction of the blue vector, as indicated by point 4.

If the pattern is concave, the direction of expansion is opposite to that of the convex point. The method of judging the concave point and the convex point is to calculate the angle between the red vector and the blue vector (counterclockwise), if it exceeds 180°, it is a concave point. Here you can use the cross product to indirectly calculate the sin value to make a judgment.

Of course, the final result cannot guarantee that the edges are parallel to the original image. If you want to be parallel, you need to divide by the sin value in the final composite vector. But later found that the actual effect is not good, it will over-magnify some particularly sharp points.

The actual effect in matlab is roughly as follows:
insert image description here

The effect actually applied to the graphics is as follows:
insert image description here
It can be seen that the effect of a perfect fit is basically achieved.

1.8 Output as SVG format

The reason for choosing the SVG format is because it uses XML language, which can be read directly by text software such as txt, which is convenient for programming and writing in matlab.
The basic syntax is:

<svg width="500" height="500"> 
具体内容
</svg> 

The main object used here is the polygon polygon, fill represents the fill color, stroke represents the line color, stroke-width represents the line width, points represent the points that make up the polygon, x and y are a group, connected end to end.

<polygon fill="rgb(186,157,45)" stroke="rgb(186,157,45)" stroke-width="1" points="326,220 326,222 328,222 328,220 326,220"/> 

2 Final result display

In the previous example, the svg conversion was tried using the images that come with matlab. Although it is not a cartoon style, it also verifies the versatility of the program.

The random avatar generator from https://getavataaars.com/ is used here as another example. This one is better suited for conversion to bitmap than the previous photo case.
insert image description here
The standard png format bitmap is shown above.
However, CSDN does not support images in SVG format, so the screenshot export method is used to display SVG format.
insert image description here
The background here is black, because the program has not yet recognized the function of transparent color. So set the transparent background to black by default.

Some corners and sharp points are somewhat distorted, which is caused by the use of median filtering before. For this kind of picture with simple painting style and clear color blocks, it is not necessary to use median filtering.

The picture below is the picture generated without median filter processing. It can be seen that almost all the original details can be well preserved. The circles around the eyes are somewhat rough, which is a defect in the polygon fitting. Some internal sharp angles disappear in the skull mouth, which is caused by the difficulty in identifying the depression when the boundary function performs edge extraction.
insert image description here
Overall, the effect has achieved the desired effect.

3 Complete Matlab code

clear
clc
close all
%把位图转换为矢量图

%% 1初始设置
%导入图片
IM_Origin=imread('peppers.png'); 

%导出名称
filename='peppers_hyh.svg';

%输出的颜色数量(约多约接近原图,不过一般卡通图本身颜色数量就不多,所以也没必要太多)
ColorNum=27;

%原始图片
figure()
imshow(IM_Origin)

%中值滤波(对于简单图案,可以不进行滤波)
IM_Origin=IM_RGB_medfilt(IM_Origin,5);%中值滤波,去除噪点

%% 2图像处理
%图像基本信息
IH=size(IM_Origin,1);%图像的高度
IW=size(IM_Origin,2);%图像的宽度
IN=IH*IW;%图像的像素总数

%图片颜色量化
%[X,cmap]=rgb2ind(IM_Origin,0.25,'nodither');
[X,cmap]=rgb2ind(IM_Origin,ColorNum,'nodither');

%去除掉周围只有2个像素的图像
X=Del_1_Pix(X);
X=Del_1_Pix(X);

%生成去除单独像素后的图像(预想结果图)
figure()
IM_Reduce2 = ind2rgb(X,cmap);
imshow(IM_Reduce2)

%对每一个颜色进行分割,保存轮廓与颜色信息
temp=0;
for k=1:size(cmap,1)
    BW_k=(X==(k-1));%索引对应的分别为0到(N-1),所以要减1
    C_BW_K=bwconncomp(BW_k);%进行区域分割识别
    Pix_Area=C_BW_K.PixelIdxList;
    N_Area=numel(Pix_Area);
    for m=1:N_Area
        Pix_Area_m=Pix_Area{m};%找到对应的区域
        [Pix_Area_m_X,Pix_Area_m_Y]=ind2sub([IH,IW],Pix_Area_m);%转化为坐标
        B_id=boundary(Pix_Area_m_X,Pix_Area_m_Y,0.9);%提取边缘,找出边界索引
        BD_Area_m_X=Pix_Area_m_X(B_id);%边界的x坐标
        BD_Area_m_Y=Pix_Area_m_Y(B_id);%边界的y坐标
        %保存
        temp=temp+1;%临时计数用
        PolySave(temp).AreaX=BD_Area_m_X;
        PolySave(temp).AreaY=BD_Area_m_Y;
        PolySave(temp).Color=cmap(k,:);
        PolySave(temp).AreaSum=numel(Pix_Area_m);%计算面积
    end
end
%按照面积排序
N_Polygon=numel(PolySave);
Area_Max2Min=zeros(N_Polygon,1);
Area_List=zeros(N_Polygon,1);
for k=1:N_Polygon
    Area_List(k)=PolySave(k).AreaSum;%提取出面积
end
[~,Area_Max2Min]=sort(Area_List,'descend');


%将每个多边形向外扩展1个像素
for k=1:N_Polygon
    if ~isempty(PolySave(k).AreaX)
        if Area_List(k)<9
            R=0.5;
        elseif Area_List(k)<16
            R=1;
        else
            R=1.5;
        end
        [xE,yE]=PolyExpand(PolySave(k).AreaX,PolySave(k).AreaY,R);
        xE(xE<1)=1;xE(xE>IH)=IH;%防止超出画布边界
        yE(yE<1)=1;yE(yE>IW)=IW;
        PolySave(k).AreaX=xE;%保存
        PolySave(k).AreaY=yE;
    end
end

%模拟利用多边形绘制矢量图(实际结果图)
figure()
set(gca,'YDir','reverse');
xlim([1,IW]);ylim([1,IH]);
axis equal
hold on
for k=1:N_Polygon
    ID_k=Area_Max2Min(k);
    fill(PolySave(ID_k).AreaY,PolySave(ID_k).AreaX,PolySave(ID_k).Color,'EdgeColor','none')
end
hold off


%% 3保存为svg文件
%创建文件
f_id=fopen(filename,'w');

s=['<svg width="',num2str(IW),'" height="',num2str(IH),'">'];
fprintf(f_id,'%s \r\n',s);
%中间添加各个多边形
for k=1:N_Polygon
    ID_k=Area_Max2Min(k);
    xSVG_k=PolySave(ID_k).AreaY;
    ySVG_k=PolySave(ID_k).AreaX;
    cSVG_k=PolySave(ID_k).Color;
    if numel(xSVG_k)>3
        %坐标转换
        str_sum='';
        for m=1:numel(xSVG_k)
            str_1=[num2str(xSVG_k(m)),',',num2str(ySVG_k(m))];
            str_sum=[str_sum,str_1,' '];
        end
        str_1=[num2str(xSVG_k(1)),',',num2str(ySVG_k(1))];
        str_sum=[str_sum,str_1];
        %颜色转换
        str_1=['rgb(',num2str(cSVG_k(1)*255),',',num2str(cSVG_k(2)*255),',',num2str(cSVG_k(3)*255),')'];
        s=['<polygon fill="',str_1,'" stroke="',str_1,'" stroke-width="1" points="',str_sum,'"/>'];
        fprintf(f_id,'%s \r\n',s);
    end

end
%结束
s=['</svg>'];
fprintf(f_id,'%s \r\n',s);
fclose(f_id);



%% 其它用到的函数
function IM2=IM_RGB_medfilt(IM1,windows)
%RGB图片的中值滤波
IM_R=medfilt2(IM1(:,:,1),[windows,windows]);%5×5的中值滤波
IM_G=medfilt2(IM1(:,:,2),[windows,windows]);
IM_B=medfilt2(IM1(:,:,3),[windows,windows]);
IM2=uint8(zeros(size(IM1)));
IM2(:,:,1)=IM_R;IM2(:,:,2)=IM_G;IM2(:,:,3)=IM_B;
end

function X3=Del_1_Pix(X)
%去除孤立的像素。如果该像素周围只有2个和它一样的像素的话,就认为它是孤立的
%耗时较长
%图像基本信息
IH=size(X,1);%图像的高度
IW=size(X,2);%图像的宽度
IN=IH*IW;%图像的像素总数
%X=double(X);不能double,浮点数从1索引,整数从0索引,不一样
X2=NaN(IH+2,IW+2);%X2只用于检索
X2(2:end-1,2:end-1)=X;
X3=X;%X3只用于更改
for k=1:IN
    [k1,k2]=ind2sub([IH,IW],k);
    k1=k1+1;k2=k2+1;%由于X2在周围加了一圈,所以索引值也要加1
    %读取周边的9个数值,NaN为辅助值
    X_Id9=X2(k1-1:k1+1,k2-1:k2+1);
    X_IdC=X_Id9(2,2);%中央的点读取
    X_Id9(2,2)=NaN;
    %删除所有的inf
    X_Id9_2=X_Id9(:);
    [NaN_Id9,~]=find(isnan(X_Id9_2));
    X_Id9_2(NaN_Id9,:)=[];
    %判断中间值和周围值是否只存在一个相等的
    if sum(X_Id9_2==X_IdC)<=2
        %如果是,把这个孤立像素替换为周围的某个点
        X3(k)=mode(X_Id9_2);
    end
end
end



function [xE,yE]=PolyExpand(x,y,R)
%将每个多边形向外扩展1个像素
%将每个顶点,沿着相邻两个边 所合成的矢量方向,移动。如果落在图形内部,则反向
%多边形不能自相交
x=x(:);
y=y(:);
%判断是否收尾相交封闭
if x(1)==x(end) && y(1)==y(end)
    x2=x;
    y2=y;
else
    x2=[x;x(1)];
    y2=[y;y(1)];
end
%把点按照指定方向进行排列
Area_xy=trapz(x2,y2);
if Area_xy>0
    x2=flipud(x2);
    y2=flipud(y2);
end
N=numel(x2);
%计算每个点与下一个点相连的向量
Dx=diff(x2);
Dy=diff(y2);
%开始按方向逐点移动
x3=[];y3=[];
for k=1:N-1
    %计算向量a和向量b
    if k==1
        Da=[Dx(1),Dy(1)];
        Db=[Dx(N-1),Dy(N-1)];
    else
        Da=[Dx(k),Dy(k)];
        Db=[Dx(k-1),Dy(k-1)];
    end
    %归一化
    Da=Da/norm(Da);
    Db=Db/norm(Db);
    %如果向量a和向量b差积大于0,则证明是个凹陷点
    Dc=cross([Da,0],[Db,0]);
    Dab_Sin=Dc(3);
    %进行扩展判断
    if Dab_Sin>0
        DR=Da-Db;%如果是凹点
        xy3=[x2(k),y2(k)]+R*DR;
        %xy3=[x2(k),y2(k)]+R*DR/abs(Dab_Sin);保形膨胀,但是实际效果反而不好
        x3=[x3;xy3(1)];
        y3=[y3,xy3(2)];
    elseif Dab_Sin<0
        DR=Db-Da;%如果是凸点
        xy3=[x2(k),y2(k)]+R*DR;
        %xy3=[x2(k),y2(k)]+R*DR/abs(Dab_Sin);保形膨胀,但是实际效果反而不好
        x3=[x3;xy3(1)];
        y3=[y3,xy3(2)];
    elseif Dc(3)==0
        ;%直接跳过
    end
end
xE=x3;
yE=y3;
end

Guess you like

Origin blog.csdn.net/weixin_42943114/article/details/114452416