纯C# WPF 32×32 文生图软件(无需单片机,开箱即用)

这是一套完全运行在Windows上位机的轻量AI生图工具,基于 .NET Framework 4.8 + WPF 开发,无任何第三方依赖、无需Python、无需单片机,内置MLP生成模型,支持文本关键词控制、参考图轮廓/风格控制,生成32×32像素灰度图,可直接预览、放大、保存图片。

一、工程创建步骤

  1. 打开 Visual Studio,新建项目 → 选择 WPF 应用(.NET Framework)
  2. 框架选择 .NET Framework 4.8,自定义项目名称后创建
  3. 右键项目 → 添加 → 引用 → 勾选 System.Drawing,确认保存
  4. 分别替换 MainWindow.xamlMainWindow.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
    }
}

四、软件使用说明

基础操作

  1. 文本控制:在输入框填写包含预设关键词的描述(如「山 像素风」「房子 简笔画」)
  2. 参考图控制(可选):点击「选择参考图片」上传本地图片,选择「轮廓控制」或「风格控制」
  3. 生成设置
    • 拖动滑块调整预览放大倍数(像素风采用最近邻放大,保持锯齿质感)
    • 勾选「固定随机种子」可每次生成完全相同的画面
  4. 点击「生成图片」,右侧预览区立即显示32×32像素生成结果
  5. 点击「保存图片到本地」可导出原始32×32分辨率的PNG文件

效果说明

  • 内置的是示例随机权重,生成的画面以抽象纹理、随机像素图案为主,能体现条件控制的趋势,但不具备真实语义生成能力
  • 若需要生成真实的小猫、房子等具体图案,只需将训练好的MLP权重替换到InitModelWeights函数中即可

五、替换训练好的权重方法

如果你通过Python训练好了生成模型,只需按以下步骤替换权重,无需修改其他逻辑:

  1. 在Python中将训练好的fc1.weight、fc1.bias等参数导出为float数组
  2. 替换代码中_fc1Weight、_fc1Bias等变量的初始化内容
  3. 重新编译软件即可获得对应风格的生成效果

六、可扩展功能

  • 支持RGB彩色图生成(将输出维度改为32×32×3即可)
  • 新增更多关键词、扩充词向量库
  • 增加批量生成、随机种子遍历功能
  • 加入像素画调色板、颜色填充工具

需要我帮你补充彩色图生成版本,或者增加更多预设的关键词和生成风格吗?

更多推荐