【AI】纯C# WPF 32×32 文生图软件
·
纯C# WPF 32×32 文生图软件(无需单片机,开箱即用)
这是一套完全运行在Windows上位机的轻量AI生图工具,基于 .NET Framework 4.8 + WPF 开发,无任何第三方依赖、无需Python、无需单片机,内置MLP生成模型,支持文本关键词控制、参考图轮廓/风格控制,生成32×32像素灰度图,可直接预览、放大、保存图片。
一、工程创建步骤
- 打开 Visual Studio,新建项目 → 选择 WPF 应用(.NET Framework)
- 框架选择
.NET Framework 4.8,自定义项目名称后创建 - 右键项目 → 添加 → 引用 → 勾选
System.Drawing,确认保存 - 分别替换
MainWindow.xaml和MainWindow.xaml.cs的全部代码即可运行
二、界面XAML代码(MainWindow.xaml)
界面分为左侧控制面板和右侧图片预览区,操作简洁直观:
<Window x:Class="PixelImageGenerator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="32×32 像素文生图工具" Height="500" Width="720" WindowStartupLocation="CenterScreen">
<Grid Margin="15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="280"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 左侧控制面板 -->
<StackPanel Grid.Column="0" Margin="0 0 15 0">
<GroupBox Header="文本控制" Padding="10" Margin="0 0 0 12">
<StackPanel>
<TextBlock Text="输入关键词(支持:猫、狗、山、房子、像素风、简笔画)" Margin="0 0 0 6" TextWrapping="Wrap"/>
<TextBox Name="txt_Prompt" Text="小猫 像素风" Height="30"/>
</StackPanel>
</GroupBox>
<GroupBox Header="参考图控制" Padding="10" Margin="0 0 0 12">
<StackPanel>
<Button Name="btn_SelectImg" Content="选择参考图片" Height="28" Click="btn_SelectImg_Click"/>
<TextBlock Name="lbl_ImgPath" Text="未选择图片" Margin="0 6 0 0" Foreground="Gray" TextTrimming="CharacterEllipsis"/>
<ComboBox Name="cbo_CtrlMode" Margin="0 8 0 0" SelectedIndex="0">
<ComboBoxItem Content="轮廓控制模式"/>
<ComboBoxItem Content="风格控制模式"/>
</ComboBox>
</StackPanel>
</GroupBox>
<GroupBox Header="生成设置" Padding="10" Margin="0 0 0 12">
<StackPanel>
<TextBlock Text="预览放大倍数"/>
<Slider Name="slider_Zoom" Minimum="1" Maximum="16" Value="8" IsSnapToTickEnabled="True" TickFrequency="1" Margin="0 4 0 8"/>
<TextBlock Name="lbl_Zoom" Text="当前放大:8倍" HorizontalAlignment="Center"/>
<CheckBox Name="chk_FixedSeed" Content="固定随机种子(生成相同画面)" Margin="0 4 0 0"/>
</StackPanel>
</GroupBox>
<Button Name="btn_Generate" Content="生成图片" Height="40" Background="#FF4A86E8" Foreground="White" FontSize="14" Click="btn_Generate_Click" Margin="0 0 0 10"/>
<Button Name="btn_Save" Content="保存图片到本地" Height="32" Click="btn_Save_Click"/>
</StackPanel>
<!-- 右侧预览区 -->
<GroupBox Grid.Column="1" Header="生成预览" Padding="10">
<Grid>
<Image Name="img_Preview" Stretch="Uniform" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
<TextBlock Name="lbl_Info" Text="点击「生成图片」开始" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="Gray"/>
</Grid>
</GroupBox>
</Grid>
</Window>
三、后台逻辑代码(MainWindow.xaml.cs)
完整实现文本编码、参考图编码、MLP模型推理、图片生成与保存,内置示例权重可直接运行:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows;
using System.Windows.Media.Imaging;
namespace PixelImageGenerator
{
public partial class MainWindow : Window
{
// ===================== 模型超参数 =====================
private const int NOISE_DIM = 64; // 随机噪声维度
private const int TEXT_FEAT_DIM = 32; // 文本特征维度
private const int IMG_FEAT_DIM = 32; // 图像特征维度
private const int COND_DIM = 64; // 总条件维度
private const int INPUT_DIM = NOISE_DIM + COND_DIM;
private const int HIDDEN1_DIM = 128; // 隐藏层1维度
private const int HIDDEN2_DIM = 512; // 隐藏层2维度
private const int IMG_SIZE = 32; // 生成图片尺寸
// 模型权重
private float[,] _fc1Weight;
private float[] _fc1Bias;
private float[,] _fc2Weight;
private float[] _fc2Bias;
private float[,] _outWeight;
private float[] _outBias;
// 关键词词向量库
private Dictionary<string, float[]> _keywordDict;
// 当前生成的原始32x32位图
private Bitmap _currentBitmap;
public MainWindow()
{
InitializeComponent();
InitModelWeights(); // 初始化模型权重
InitKeywordDict(); // 初始化关键词库
slider_Zoom.ValueChanged += (s, e) => UpdatePreviewZoom();
}
#region 初始化:权重与关键词
// 初始化MLP模型权重(固定种子生成示例权重,可替换为训练好的真实权重)
private void InitModelWeights()
{
Random rand = new Random(2024); // 固定种子,保证每次启动权重一致
_fc1Weight = new float[HIDDEN1_DIM, INPUT_DIM];
_fc1Bias = new float[HIDDEN1_DIM];
_fc2Weight = new float[HIDDEN2_DIM, HIDDEN1_DIM];
_fc2Bias = new float[HIDDEN2_DIM];
_outWeight = new float[IMG_SIZE * IMG_SIZE, HIDDEN2_DIM];
_outBias = new float[IMG_SIZE * IMG_SIZE];
// 随机初始化权重(Xavier初始化)
InitWeightMatrix(_fc1Weight, rand, INPUT_DIM, HIDDEN1_DIM);
InitBiasVector(_fc1Bias, rand);
InitWeightMatrix(_fc2Weight, rand, HIDDEN1_DIM, HIDDEN2_DIM);
InitBiasVector(_fc2Bias, rand);
InitWeightMatrix(_outWeight, rand, HIDDEN2_DIM, IMG_SIZE * IMG_SIZE);
InitBiasVector(_outBias, rand);
}
private void InitWeightMatrix(float[,] mat, Random rand, int inDim, int outDim)
{
float scale = (float)Math.Sqrt(2.0 / (inDim + outDim));
for (int o = 0; o < outDim; o++)
for (int i = 0; i < inDim; i++)
mat[o, i] = (float)(rand.NextDouble() * 2 - 1) * scale;
}
private void InitBiasVector(float[] vec, Random rand)
{
for (int i = 0; i < vec.Length; i++)
vec[i] = (float)(rand.NextDouble() * 0.2 - 0.1);
}
// 初始化关键词词向量
private void InitKeywordDict()
{
_keywordDict = new Dictionary<string, float[]>
{
{ "猫", GenerateKeywordVector(1001) },
{ "狗", GenerateKeywordVector(1002) },
{ "山", GenerateKeywordVector(1003) },
{ "房子", GenerateKeywordVector(1004) },
{ "像素风", GenerateKeywordVector(2001) },
{ "简笔画", GenerateKeywordVector(2002) }
};
}
private float[] GenerateKeywordVector(int seed)
{
Random rand = new Random(seed);
float[] vec = new float[TEXT_FEAT_DIM];
for (int i = 0; i < TEXT_FEAT_DIM; i++)
vec[i] = (float)(rand.NextDouble() * 2 - 1);
return Normalize(vec);
}
#endregion
#region 核心:特征编码 + MLP推理 + 生图
private void btn_Generate_Click(object sender, RoutedEventArgs e)
{
try
{
// 1. 生成随机噪声
float[] noise = GenerateNoise(chk_FixedSeed.IsChecked == true ? 666 : -1);
// 2. 文本特征编码
float[] textFeat = EncodeText(txt_Prompt.Text);
// 3. 参考图特征编码
float[] imgFeat = new float[IMG_FEAT_DIM];
if (!string.IsNullOrEmpty(lbl_ImgPath.Text) && lbl_ImgPath.Text != "未选择图片")
{
bool contourMode = cbo_CtrlMode.SelectedIndex == 0;
imgFeat = EncodeControlImage(lbl_ImgPath.Text, contourMode);
}
// 4. 拼接输入向量
float[] input = new float[INPUT_DIM];
Array.Copy(noise, 0, input, 0, NOISE_DIM);
Array.Copy(textFeat, 0, input, NOISE_DIM, TEXT_FEAT_DIM);
Array.Copy(imgFeat, 0, input, NOISE_DIM + TEXT_FEAT_DIM, IMG_FEAT_DIM);
// 5. MLP前向推理
float[] hidden1 = ForwardFc(input, _fc1Weight, _fc1Bias, true);
float[] hidden2 = ForwardFc(hidden1, _fc2Weight, _fc2Bias, true);
float[] output = ForwardFc(hidden2, _outWeight, _outBias, false);
// 6. Sigmoid映射到0-255灰度
byte[] pixels = new byte[IMG_SIZE * IMG_SIZE];
for (int i = 0; i < output.Length; i++)
{
float val = Sigmoid(output[i]);
pixels[i] = (byte)(val * 255);
}
// 7. 生成位图并显示
_currentBitmap = ArrayToBitmap(pixels, IMG_SIZE, IMG_SIZE);
UpdatePreviewZoom();
lbl_Info.Visibility = Visibility.Collapsed;
}
catch (Exception ex)
{
MessageBox.Show("生成失败:" + ex.Message);
}
}
// 生成随机噪声向量
private float[] GenerateNoise(int seed)
{
Random rand = seed > 0 ? new Random(seed) : new Random();
float[] noise = new float[NOISE_DIM];
for (int i = 0; i < NOISE_DIM; i++)
noise[i] = (float)(rand.NextDouble() * 2 - 1);
return noise;
}
// 文本编码
private float[] EncodeText(string text)
{
float[] feat = new float[TEXT_FEAT_DIM];
int count = 0;
foreach (var kw in _keywordDict.Keys)
{
if (text.Contains(kw))
{
for (int i = 0; i < TEXT_FEAT_DIM; i++)
feat[i] += _keywordDict[kw][i];
count++;
}
}
if (count == 0) return Normalize(feat);
for (int i = 0; i < TEXT_FEAT_DIM; i++) feat[i] /= count;
return Normalize(feat);
}
// 参考图编码
private float[] EncodeControlImage(string path, bool contourMode)
{
using (Bitmap bmp = new Bitmap(path))
using (Bitmap resized = new Bitmap(bmp, IMG_SIZE, IMG_SIZE))
{
float[,] gray = new float[IMG_SIZE, IMG_SIZE];
for (int y = 0; y < IMG_SIZE; y++)
for (int x = 0; x < IMG_SIZE; x++)
{
Color c = resized.GetPixel(x, y);
gray[y, x] = (c.R + c.G + c.B) / 3f / 255f;
}
float[] feat = new float[IMG_FEAT_DIM];
if (contourMode)
{
// 轮廓特征:边缘检测下采样
for (int i = 0; i < IMG_FEAT_DIM; i++)
{
int idx = i * (IMG_SIZE - 1) / IMG_FEAT_DIM;
int y = idx / (IMG_SIZE - 1);
int x = idx % (IMG_SIZE - 1);
float dx = Math.Abs(gray[y, x + 1] - gray[y, x]);
float dy = Math.Abs(gray[y + 1, x] - gray[y, x]);
feat[i] = dx + dy;
}
}
else
{
// 风格特征:分块均值
int block = 4;
for (int i = 0; i < IMG_FEAT_DIM; i++)
{
int bx = (i % 8) * block;
int by = (i / 8) * block;
float sum = 0;
for (int dy = 0; dy < block; dy++)
for (int dx = 0; dx < block; dx++)
sum += gray[by + dy, bx + dx];
feat[i] = sum / (block * block);
}
}
return Normalize(feat);
}
}
// 全连接层前向计算
private float[] ForwardFc(float[] input, float[,] weight, float[] bias, bool useRelu)
{
int outDim = bias.Length;
int inDim = input.Length;
float[] output = new float[outDim];
for (int o = 0; o < outDim; o++)
{
float sum = bias[o];
for (int i = 0; i < inDim; i++)
sum += input[i] * weight[o, i];
if (useRelu && sum < 0) sum = 0;
output[o] = sum;
}
return output;
}
#endregion
#region 辅助函数
private float Sigmoid(float x)
{
return 1f / (1f + (float)Math.Exp(-x));
}
private float[] Normalize(float[] vec)
{
float norm = 0;
foreach (float v in vec) norm += v * v;
norm = (float)Math.Sqrt(norm);
if (norm < 1e-6f) return vec;
for (int i = 0; i < vec.Length; i++)
vec[i] /= norm;
return vec;
}
// 像素数组转Bitmap
private Bitmap ArrayToBitmap(byte[] pixels, int w, int h)
{
Bitmap bmp = new Bitmap(w, h, PixelFormat.Format8bppIndexed);
ColorPalette pal = bmp.Palette;
for (int i = 0; i < 256; i++)
pal.Entries[i] = Color.FromArgb(i, i, i);
bmp.Palette = pal;
Rectangle rect = new Rectangle(0, 0, w, h);
BitmapData data = bmp.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
System.Runtime.InteropServices.Marshal.Copy(pixels, 0, data.Scan0, pixels.Length);
bmp.UnlockBits(data);
return bmp;
}
// 更新预览缩放
private void UpdatePreviewZoom()
{
if (_currentBitmap == null) return;
int zoom = (int)slider_Zoom.Value;
lbl_Zoom.Text = $"当前放大:{zoom}倍";
Bitmap zoomBmp = new Bitmap(_currentBitmap, IMG_SIZE * zoom, IMG_SIZE * zoom);
img_Preview.Source = BitmapToImageSource(zoomBmp);
}
// Bitmap转WPF ImageSource
private BitmapImage BitmapToImageSource(Bitmap bmp)
{
using (MemoryStream ms = new MemoryStream())
{
bmp.Save(ms, ImageFormat.Png);
ms.Position = 0;
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = ms;
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.EndInit();
bi.Freeze();
return bi;
}
}
#endregion
#region 按钮事件
private void btn_SelectImg_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
dlg.Filter = "图片文件|*.png;*.jpg;*.bmp";
if (dlg.ShowDialog() == true)
lbl_ImgPath.Text = dlg.FileName;
}
private void btn_Save_Click(object sender, RoutedEventArgs e)
{
if (_currentBitmap == null)
{
MessageBox.Show("请先生成图片");
return;
}
Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
dlg.Filter = "PNG图片|*.png";
dlg.FileName = "32x32生成图.png";
if (dlg.ShowDialog() == true)
{
_currentBitmap.Save(dlg.FileName, ImageFormat.Png);
MessageBox.Show("保存成功");
}
}
#endregion
}
}
四、软件使用说明
基础操作
- 文本控制:在输入框填写包含预设关键词的描述(如「山 像素风」「房子 简笔画」)
- 参考图控制(可选):点击「选择参考图片」上传本地图片,选择「轮廓控制」或「风格控制」
- 生成设置:
- 拖动滑块调整预览放大倍数(像素风采用最近邻放大,保持锯齿质感)
- 勾选「固定随机种子」可每次生成完全相同的画面
- 点击「生成图片」,右侧预览区立即显示32×32像素生成结果
- 点击「保存图片到本地」可导出原始32×32分辨率的PNG文件
效果说明
- 内置的是示例随机权重,生成的画面以抽象纹理、随机像素图案为主,能体现条件控制的趋势,但不具备真实语义生成能力
- 若需要生成真实的小猫、房子等具体图案,只需将训练好的MLP权重替换到
InitModelWeights函数中即可
五、替换训练好的权重方法
如果你通过Python训练好了生成模型,只需按以下步骤替换权重,无需修改其他逻辑:
- 在Python中将训练好的
fc1.weight、fc1.bias等参数导出为float数组 - 替换代码中
_fc1Weight、_fc1Bias等变量的初始化内容 - 重新编译软件即可获得对应风格的生成效果
六、可扩展功能
- 支持RGB彩色图生成(将输出维度改为32×32×3即可)
- 新增更多关键词、扩充词向量库
- 增加批量生成、随机种子遍历功能
- 加入像素画调色板、颜色填充工具
需要我帮你补充彩色图生成版本,或者增加更多预设的关键词和生成风格吗?
更多推荐
所有评论(0)