目录
2. 使用pyinstaller将labelme打包为exe文件
背景
由于工作原因,需要对图像进行分类,建模,作为深度学习的材料。自己造轮子造了半天,结果发现labelme这块做的很好,于是放弃造轮子,借用labelme产生的json文件,生成需要的8位黑白png图(labelme自带的生成8位png图是彩色的,不满足需求)。
软件环境
Win10 python3.8.2 labelme4.2.10 pyqt5.14.1 pip-20.0.2
黑白图像素填充原理
根据json文件中的label标签,随机生成不同的1-255之间的数值,作为region的像素值。背景像素全为0
源码
解析JSON文件,我添加了Newtonsoft.Json.dll引用。可以直接在NuGet中搜索添加。
System.Drawing也需要在“程序集-框架”中添加。
以下为程序源码
Program.cs文件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace labelme_Json_to_png8
{
class Program
{
/// <summary>
/// 根据labelme-4.2.10产生的json文件批量生成png图片
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
if(args.Length==2)
{
string strPathSrc = args[0]; //labelme产生的JSON文件所在文件夹
string strPathDes = args[1]; //产生的PNG文件存放的文件夹
try
{
List<string> srcFileNames = labelmeJson.GetJsonfiles(strPathSrc);
int srcNum = srcFileNames.Count;
for (int i = 0; i < srcNum; i++)
{
string strDes = Path.Combine(strPathDes, Path.GetFileNameWithoutExtension(srcFileNames[i])+".png");
//desFileNames.Add(str);
labelmeJson.CreateImage(srcFileNames[i], strDes);
}
Console.WriteLine("Convert OK! 目标文件夹地址:" + strPathDes);
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
Console.ReadKey();
}
}
}
}
labelmeJson.cs文件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace labelme_Json_to_png8
{
public class labelmeJson
{
private static PointF[] GetPointFs(List<List<double>> listDou)
{
int inum = listDou.Count;
PointF[] pointf = new PointF[inum];
for (int i = 0; i < inum; i++)
{
PointF p = new PointF();
p.X = (float)listDou[i][0];
p.Y = (float)listDou[i][1];
pointf[i] = p;
}
return pointf;
}
/// <summary>
/// 随机给各个region赋1-255之间像素值,背景色像素值为0
/// </summary>
/// <param name="strSourcePath"></param>
/// <param name="strDesPath"></param>
/// <returns></returns>
public static bool CreateImage(string strSourcePath, string strDesPath)
{
try
{
string jsonstr = File.ReadAllText(strSourcePath);
LabelmeJsonObject lbjs = JsonConvert.DeserializeObject<LabelmeJsonObject>(jsonstr);
//确保图像的宽度是4的倍数
int imageWidth = int.Parse(lbjs.imageWidth);
int imageHeight = int.Parse(lbjs.imageHeight);
Bitmap bit = new Bitmap(imageWidth, imageHeight);
Graphics dc = Graphics.FromImage(bit);
dc.Clear(Color.Black);
Dictionary<string, int> dicLabel_Val = new Dictionary<string, int>();
Random random = new Random();
foreach (Shapes shape in lbjs.shapes)
{
string label = shape.label;
if (!dicLabel_Val.Keys.Contains(label))
{
int num = random.Next(1, 255);
dicLabel_Val.Add(label, num);
}
}
foreach (Shapes shape in lbjs.shapes)
{
int rval = dicLabel_Val[shape.label];
if (shape.shape_type == "polygon")
{
PointF[] pointf = GetPointFs(shape.points);
dc.FillPolygon(new SolidBrush(Color.FromArgb(rval, 0, 0)), pointf);
dc.Save();
}
else if (shape.shape_type == "linestrip")
{
PointF[] pointf = GetPointFs(shape.points);
dc.DrawLines(new Pen(new SolidBrush(Color.FromArgb(rval, 0, 0))), pointf);
dc.Save();
}
else if (shape.shape_type == "rectangle")
{
PointF[] pointf = GetPointFs(shape.points);
Rectangle rect = new Rectangle((int)pointf[0].X, (int)pointf[0].Y, (int)(pointf[1].X - pointf[0].X), (int)(pointf[1].Y - pointf[0].Y));
dc.FillRectangle(new SolidBrush(Color.FromArgb(rval, 0, 0)), rect);
dc.Save();
}
else if (shape.shape_type == "circle")
{
PointF[] pointf = GetPointFs(shape.points);
double f = Math.Pow((pointf[1].X - pointf[0].X), 2) + Math.Pow((pointf[1].Y - pointf[0].Y), 2);
float r = (float)Math.Sqrt(f);
dc.FillEllipse(new SolidBrush(Color.FromArgb(rval, 0, 0)), pointf[0].X, pointf[0].Y, r, r);
dc.Save();
}
else if (shape.shape_type == "point")
{
PointF[] pointf = GetPointFs(shape.points);
dc.FillEllipse(new SolidBrush(Color.FromArgb(rval, 0, 0)), pointf[0].X, pointf[0].Y, 2, 2);
dc.Save();
}
}
Bitmap bmp = RgbToR8(bit);
bmp.Save(strDesPath, ImageFormat.Png);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
return false;
}
return true;
}
/// <summary>
/// 获取当前目录下所有的json文件
/// </summary>
/// <param name="strPath"></param>
/// <returns></returns>
public static List<string> GetJsonfiles(string strPath)
{
List<string> list = new List<string>();
DirectoryInfo theFolder = new DirectoryInfo(strPath); // 给出你的目录文件位置
FileInfo[] fileInfo = theFolder.GetFiles(); // 获得当前的文件夹内的所有文件数组
foreach (FileInfo NextFile in fileInfo) //遍历文件
{
if (NextFile.Extension.ToLower() == ".json") // 得到你想要的格式
{
list.Add(NextFile.FullName); // 用于测试输出
}
}
return list;
}
/// <summary>
/// 取源图像R通道数据,并转化为8位单像素图像。
/// </summary>
/// <param name="original"> 源图像。 </param>
/// <returns> 8位单像素图像。 </returns>
public static Bitmap RgbToR8(Bitmap original)
{
if (original != null)
{
// 将源图像内存区域锁定
Rectangle rect = new Rectangle(0, 0, original.Width, original.Height);
BitmapData bmpData = original.LockBits(rect, ImageLockMode.ReadOnly,
PixelFormat.Format24bppRgb);
// 获取图像参数
int width = bmpData.Width;
int height = bmpData.Height;
int stride = bmpData.Stride; // 扫描线的宽度,比实际图片要大
int offset = stride - width * 3; // 显示宽度与扫描线宽度的间隙
IntPtr ptr = bmpData.Scan0; // 获取bmpData的内存起始位置的指针
int scanBytesLength = stride * height; // 用stride宽度,表示这是内存区域的大小
// 分别设置两个位置指针,指向源数组和目标数组
int posScan = 0, posDst = 0;
byte[] rgbValues = new byte[scanBytesLength]; // 为目标数组分配内存
Marshal.Copy(ptr, rgbValues, 0, scanBytesLength); // 将图像数据拷贝到rgbValues中
// 分配单像素数组
byte[] grayValues = new byte[width * height]; // 不含未用空间。
// 计算单像素数组
byte blue, green, red;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
blue = rgbValues[posScan];
green = rgbValues[posScan + 1];
red = rgbValues[posScan + 2];
grayValues[posDst] = red;
posScan += 3;
posDst++;
}
// 跳过图像数据每行未用空间的字节,length = stride - width * bytePerPixel
posScan += offset;
}
// 内存解锁
Marshal.Copy(rgbValues, 0, ptr, scanBytesLength);
original.UnlockBits(bmpData); // 解锁内存区域
// 构建8位单像素位图
Bitmap retBitmap = BuiltGrayBitmap(grayValues, width, height);
return retBitmap;
}
else
{
return null;
}
}
/// <summary>
/// 用数组新建一个8位图像。
/// </summary>
/// <param name="rawValues"> 像素数组(length = width * height)。 </param>
/// <param name="width"> 图像宽度。 </param>
/// <param name="height"> 图像高度。 </param>
/// <returns> 新建的8位单像素位图。 </returns>
private static Bitmap BuiltGrayBitmap(byte[] rawValues, int width, int height)
{
// 新建一个8位位图,并锁定内存区域操作
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, width, height),
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
// 计算图像参数
int offset = bmpData.Stride - bmpData.Width; // 计算每行未用空间字节数
IntPtr ptr = bmpData.Scan0; // 获取首地址
int scanBytes = bmpData.Stride * bmpData.Height; // 图像字节数 = 扫描字节数 * 高度
byte[] grayValues = new byte[scanBytes]; // 为图像数据分配内存
// 为图像数据赋值
int posSrc = 0, posScan = 0; // rawValues和grayValues的索引
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
grayValues[posScan++] = rawValues[posSrc++];
}
// 跳过图像数据每行未用空间的字节,length = stride - width * bytePerPixel
posScan += offset;
}
// 内存解锁
Marshal.Copy(grayValues, 0, ptr, scanBytes);
bitmap.UnlockBits(bmpData); // 解锁内存区域
// 修改生成位图的索引表,从伪彩修改为单像素
ColorPalette palette;
// 获取一个Format8bppIndexed格式图像的Palette对象
using (Bitmap bmp = new Bitmap(1, 1, PixelFormat.Format8bppIndexed))
{
palette = bmp.Palette;
}
for (int i = 0; i < 256; i++)
{
palette.Entries[i] = Color.FromArgb(i, i, i);
}
// 修改生成位图的索引表
bitmap.Palette = palette;
return bitmap;
}
}
public class Flags { }
public class Shapes
{
public string label { get; set; }
public List<List<double>> points { get; set; }
public string group_id { get; set; }
public string shape_type { get; set; }
public Flags flags { get; set; }
}
public class LabelmeJsonObject
{
public string version { get; set; }
public Flags flags { get; set; }
public List<Shapes> shapes { get; set; }
public string imagePath { get; set; }
public string imageData { get; set; }
public string imageHeight { get; set; }
public string imageWidth { get; set; }
}
}
示例图片
附录:
1.调试带参数的控制台应用程序方法
在项目属性中选中 “调试” 页,在 “启动选项-命令行参数”栏中写入 json文件所在文件夹 和 产生的png文件保存的文件夹。和在cmd中输入参数格式一样,注意其中的空格。例如:d:/img/json d:/img/json/png
在“启动选项-工作目录”栏中,选择程序所在目录,例如:程序的debug目录。
2. 使用pyinstaller将labelme打包为exe文件
https://blog.youkuaiyun.com/weixin_44001860/article/details/103746555
3. 数据标注软件labelme详解
https://blog.youkuaiyun.com/u014061630/article/details/88756644