DeOldify赋能微信小程序:老照片上色功能的快速实现

📅 发布时间:2026/7/6 13:05:21 👁️ 浏览次数:
DeOldify赋能微信小程序:老照片上色功能的快速实现
DeOldify赋能微信小程序老照片上色功能的快速实现家里的老相册总藏着几页泛黄的黑白照片。那是爷爷奶奶的结婚照是父母年轻时的模样是我们回不去的童年。这些照片承载着记忆却因为褪色而显得有些遥远。现在我们完全可以用技术为它们“一键上色”让记忆重新鲜活起来。今天我就带你一步步搭建一个微信小程序让用户上传一张黑白老照片几分钟后就能收到一张经过AI智能上色的彩色照片。整个过程前端用微信小程序后端用Java/SpringBoot而核心的AI上色能力则交给部署在星图平台上的DeOldify模型。这是一个非常典型的全栈应用案例既有前端交互又有后端服务还集成了AI能力实用性很强。1. 项目整体思路一张图看懂流程在动手写代码之前我们先理清整个小程序是怎么跑起来的。你可以把它想象成一个照片修复工厂的流水线用户在小程序端选择或拍摄一张黑白/褪色照片点击“开始上色”。小程序前端将这张照片上传到我们自己的后端服务器。后端服务器SpringBoot收到照片后先存起来然后立刻向星图平台的DeOldify API发起一个上色请求并把任务ID记录下来。星图平台DeOldify服务在云端调用强大的DeOldify模型对照片进行智能上色处理。这个过程可能需要几秒到几十秒。后端轮询与回调由于上色是异步任务需要等待我们的后端会每隔几秒去星图平台查询一次“我那个任务ID是xxx处理好了吗” 一旦星图平台返回“处理完成”并提供彩色结果图的下载链接后端就马上去把这个彩色图片下载回来。结果推送后端把下载好的彩色图片保存好生成一个能让小程序访问的链接。然后它可以通过微信小程序的模板消息或者更简单的在小程序前端轮询的机制下把这个结果链接返回给小程序。用户在小程序端看到“上色完成”的提示点击查看一张崭新的彩色照片就展现在眼前了。整个流程的核心在于异步处理上传→触发任务→等待→获取结果。我们的代码主要就是围绕这个流程来搭建桥梁。2. 后端核心SpringBoot服务搭建后端是整个应用的中枢负责接收图片、调用AI、管理任务状态和返回结果。我们使用SpringBoot来快速构建。2.1 项目初始化与依赖首先创建一个SpringBoot项目。在pom.xml文件中我们需要引入几个关键的依赖dependencies !-- Web支持 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 用于方便地处理HTTP请求比如调用星图API -- dependency groupIdorg.apache.httpcomponents/groupId artifactIdhttpclient/artifactId scopetest/scope /dependency !-- 也可以使用OkHttp或RestTemplate这里以HttpClient为例 -- !-- 用于生成唯一任务ID -- dependency groupIdorg.apache.commons/groupId artifactIdcommons-lang3/artifactId /dependency !-- 配置文件处理 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-configuration-processor/artifactId optionaltrue/optional /dependency /dependencies2.2 关键代码文件上传与AI任务调度我们主要创建两个核心的控制器Controller一个用于接收小程序上传的图片另一个用于让小程序查询处理结果。1. 文件上传控制器 (FileUploadController)这个接口负责接收小程序上传的照片并触发AI上色任务。RestController RequestMapping(/api/photo) public class FileUploadController { Autowired private DeOldifyService deOldifyService; // 核心服务类后面会写 PostMapping(/upload) public ApiResponse uploadPhoto(RequestParam(file) MultipartFile file, RequestParam(userId) String userId) { if (file.isEmpty()) { return ApiResponse.error(请选择要上传的照片); } try { // 1. 保存上传的原始图片到服务器临时目录 String originalFileName file.getOriginalFilename(); String fileExtension originalFileName.substring(originalFileName.lastIndexOf(.)); String savedFileName UUID.randomUUID().toString() fileExtension; Path savePath Paths.get(/tmp/uploads, savedFileName); Files.createDirectories(savePath.getParent()); Files.write(savePath, file.getBytes()); // 2. 调用服务提交照片到DeOldify进行处理并获取任务ID String taskId deOldifyService.submitColorizationTask(savePath.toString(), userId); // 3. 返回任务ID给小程序小程序凭此ID查询结果 MapString, String data new HashMap(); data.put(taskId, taskId); data.put(message, 照片已接收正在上色中请稍后查询结果); return ApiResponse.success(data); } catch (IOException e) { e.printStackTrace(); return ApiResponse.error(文件保存失败); } catch (Exception e) { e.printStackTrace(); return ApiResponse.error(提交上色任务失败); } } }2. 结果查询控制器 (TaskQueryController)小程序拿到taskId后会定期比如每5秒调用这个接口询问“我的照片上色好了吗”RestController RequestMapping(/api/task) public class TaskQueryController { Autowired private DeOldifyService deOldifyService; GetMapping(/status) public ApiResponse getTaskStatus(RequestParam String taskId) { TaskResult result deOldifyService.getTaskResult(taskId); if (result null) { return ApiResponse.error(未找到该任务); } MapString, Object data new HashMap(); data.put(taskId, taskId); data.put(status, result.getStatus()); // 状态 processing, success, failed data.put(message, result.getMessage()); // 如果任务成功返回彩色图片的访问地址 if (success.equals(result.getStatus())) { data.put(coloredImageUrl, result.getColoredImageUrl()); } return ApiResponse.success(data); } }2.3 核心服务与星图DeOldify API对话DeOldifyService是后端最核心的部分它封装了与星图平台API的所有交互逻辑。这里假设星图平台提供了标准的RESTful API包含“提交任务”和“查询任务结果”两个端点。Service public class DeOldifyService { // 这些配置应该放在application.yml中 Value(${starchart.api.base-url}) private String apiBaseUrl; Value(${starchart.api.key}) private String apiKey; // 用一个Map在内存中模拟任务状态存储生产环境应该用Redis或数据库 private ConcurrentHashMapString, TaskResult taskStore new ConcurrentHashMap(); /** * 提交上色任务到星图平台 */ public String submitColorizationTask(String imagePath, String userId) throws Exception { String taskId TASK_ System.currentTimeMillis() _ userId; // 1. 构建提交任务的请求体根据星图平台API文档调整 MapString, Object requestBody new HashMap(); requestBody.put(image_url, uploadToTempUrl(imagePath)); // 需要先将图片上传到一个可公网访问的临时地址或使用Base64 requestBody.put(task_id, taskId); // 2. 调用星图API的提交接口 String submitUrl apiBaseUrl /v1/colorize/submit; HttpHeaders headers new HttpHeaders(); headers.set(Authorization, Bearer apiKey); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntityMapString, Object requestEntity new HttpEntity(requestBody, headers); RestTemplate restTemplate new RestTemplate(); ResponseEntityMap response restTemplate.postForEntity(submitUrl, requestEntity, Map.class); // 3. 初始化任务状态 TaskResult taskResult new TaskResult(); taskResult.setTaskId(taskId); taskResult.setStatus(processing); taskResult.setMessage(任务已提交等待处理); taskStore.put(taskId, taskResult); // 4. 启动一个异步线程去轮询任务结果生产环境建议用消息队列或定时任务 new Thread(() - pollTaskResult(taskId)).start(); return taskId; } /** * 轮询查询任务结果 */ private void pollTaskResult(String taskId) { String queryUrl apiBaseUrl /v1/colorize/result?task_id taskId; HttpHeaders headers new HttpHeaders(); headers.set(Authorization, Bearer apiKey); boolean isFinished false; int maxRetries 60; // 最多轮询60次 int interval 3000; // 每次间隔3秒 for (int i 0; i maxRetries !isFinished; i) { try { Thread.sleep(interval); RestTemplate restTemplate new RestTemplate(); HttpEntityString entity new HttpEntity(headers); ResponseEntityMap response restTemplate.exchange(queryUrl, HttpMethod.GET, entity, Map.class); MapString, Object resultBody response.getBody(); String status (String) resultBody.get(status); TaskResult taskResult taskStore.get(taskId); if (completed.equals(status)) { // 任务完成下载彩色图片并保存到本地或云存储 String coloredImageUrl (String) resultBody.get(result_url); String savedUrl downloadAndSaveImage(coloredImageUrl, taskId); taskResult.setStatus(success); taskResult.setColoredImageUrl(savedUrl); // 返回给小程序访问的地址 taskResult.setMessage(照片上色完成); isFinished true; } else if (failed.equals(status)) { taskResult.setStatus(failed); taskResult.setMessage(上色处理失败: resultBody.get(error)); isFinished true; } // 如果状态是processing继续轮询 } catch (Exception e) { e.printStackTrace(); TaskResult taskResult taskStore.get(taskId); taskResult.setStatus(failed); taskResult.setMessage(查询任务结果时发生异常); isFinished true; } } // 超时处理 if (!isFinished) { TaskResult taskResult taskStore.get(taskId); if (processing.equals(taskResult.getStatus())) { taskResult.setStatus(failed); taskResult.setMessage(任务处理超时); } } } public TaskResult getTaskResult(String taskId) { return taskStore.get(taskId); } // 辅助方法下载图片并保存 private String downloadAndSaveImage(String imageUrl, String taskId) throws IOException { // 实现从imageUrl下载图片保存到服务器或云存储如OSS // 返回一个能通过公网访问的URL例如http://your-domain.com/results/colored_taskId.jpg // 这里简化返回一个示例URL return /results/colored_ taskId .jpg; } }这个服务类做了几件关键事封装API调用、管理任务状态、异步轮询结果。在生产环境中轮询部分最好改用更可靠的消息队列或事件驱动机制。3. 前端核心微信小程序页面开发小程序端主要负责友好的用户交互上传界面、等待提示、结果展示。3.1 页面布局与样式 (index.wxml)我们设计一个简单的页面包含上传按钮、预览区域和状态提示。!-- index.wxml -- view classcontainer view classheader text classtitle老照片上色助手/text text classsubtitle上传黑白照片AI为您还原色彩/text /view view classupload-area bindtapchooseImage wx:if{{!originalImage}} image src/images/upload-icon.png classupload-icon/image text classupload-text点击上传黑白/老照片/text text classupload-hint支持JPG、PNG格式建议尺寸小于5MB/text /view view classpreview-section wx:if{{originalImage}} view classimage-comparison view classimage-box text classimage-label原图/text image src{{originalImage}} modewidthFix classpreview-image/image /view view classimage-box wx:if{{coloredImage}} text classimage-label上色后/text image src{{coloredImage}} modewidthFix classpreview-image/image /view /view button typeprimary bindtapstartColorization loading{{isProcessing}} disabled{{isProcessing}} {{isProcessing ? 上色中... : 开始AI上色}} /button button typedefault bindtapreset wx:if{{!isProcessing}}重新选择照片/button /view view classstatus-message wx:if{{statusMessage}} text{{statusMessage}}/text /view view classtips text使用提示\n1. 照片人物面部清晰效果更佳\n2. 处理通常需要30-60秒\n3. 结果图片可长按保存/text /view /view3.2 页面逻辑与交互 (index.js)小程序的JavaScript逻辑处理图片选择、上传、轮询查询等。// index.js Page({ data: { originalImage: , // 上传的原图临时路径 coloredImage: , // 上色后的图片URL isProcessing: false, // 是否正在处理中 statusMessage: , // 状态提示信息 currentTaskId: null, // 当前任务ID pollTimer: null // 轮询定时器 }, // 选择图片 chooseImage: function() { const that this; wx.chooseImage({ count: 1, sizeType: [compressed], // 压缩图 sourceType: [album, camera], success(res) { const tempFilePath res.tempFilePaths[0]; that.setData({ originalImage: tempFilePath, coloredImage: , statusMessage: 照片已选择点击下方按钮开始上色 }); } }) }, // 开始上色处理 startColorization: function() { const that this; const filePath this.data.originalImage; if (!filePath) { wx.showToast({ title: 请先选择照片, icon: none }); return; } this.setData({ isProcessing: true, statusMessage: 正在上传并处理照片请稍候... }); // 1. 上传图片到后端服务器 wx.uploadFile({ url: https://your-backend-domain.com/api/photo/upload, // 替换为你的后端地址 filePath: filePath, name: file, formData: { userId: wx.getStorageSync(userId) || anonymous_ Date.now() }, success(res) { const data JSON.parse(res.data); if (data.code 200) { const taskId data.data.taskId; that.setData({ currentTaskId: taskId, statusMessage: 照片上传成功AI正在努力上色中... }); // 2. 开始轮询查询任务结果 that.startPollingTaskResult(taskId); } else { wx.showToast({ title: 上传失败 data.message, icon: none }); that.setData({ isProcessing: false, statusMessage: }); } }, fail() { wx.showToast({ title: 网络请求失败, icon: none }); that.setData({ isProcessing: false, statusMessage: }); } }); }, // 轮询查询任务状态 startPollingTaskResult: function(taskId) { const that this; let pollCount 0; const maxPollCount 60; // 最多轮询60次 // 清除之前的定时器 if (this.data.pollTimer) { clearInterval(this.data.pollTimer); } const timer setInterval(() { pollCount; if (pollCount maxPollCount) { clearInterval(timer); that.setData({ isProcessing: false, statusMessage: 处理超时请稍后重试, pollTimer: null }); return; } wx.request({ url: https://your-backend-domain.com/api/task/status, // 替换为你的后端地址 method: GET, data: { taskId: taskId }, success(res) { const data res.data; if (data.code 200) { const status data.data.status; if (status success) { // 处理成功显示彩色图片 clearInterval(timer); that.setData({ isProcessing: false, coloredImage: data.data.coloredImageUrl, statusMessage: 上色完成快看看效果吧~, pollTimer: null }); wx.showToast({ title: 上色成功, icon: success }); } else if (status failed) { // 处理失败 clearInterval(timer); that.setData({ isProcessing: false, statusMessage: 上色失败 data.data.message, pollTimer: null }); wx.showToast({ title: 处理失败, icon: none }); } else { // 仍在处理中更新等待信息 that.setData({ statusMessage: AI正在处理中... (${pollCount * 3}秒) }); } } }, fail() { // 网络错误继续轮询 console.log(轮询请求失败继续尝试); } }); }, 3000); // 每3秒轮询一次 this.setData({ pollTimer: timer }); }, // 重置重新选择照片 reset: function() { if (this.data.pollTimer) { clearInterval(this.data.pollTimer); } this.setData({ originalImage: , coloredImage: , isProcessing: false, statusMessage: , currentTaskId: null, pollTimer: null }); }, onUnload: function() { // 页面卸载时清除定时器 if (this.data.pollTimer) { clearInterval(this.data.pollTimer); } } })3.3 页面样式 (index.wxss)/* index.wxss */ .container { padding: 30rpx; display: flex; flex-direction: column; align-items: center; min-height: 100vh; background-color: #f8f9fa; } .header { text-align: center; margin-bottom: 60rpx; } .title { font-size: 48rpx; font-weight: bold; color: #333; display: block; margin-bottom: 16rpx; } .subtitle { font-size: 28rpx; color: #666; } .upload-area { width: 600rpx; height: 400rpx; border: 4rpx dashed #ccc; border-radius: 24rpx; display: flex; flex-direction: column; justify-content: center; align-items: center; background-color: #fff; margin-bottom: 40rpx; } .upload-area:active { background-color: #f5f5f5; } .upload-icon { width: 120rpx; height: 120rpx; margin-bottom: 30rpx; opacity: 0.6; } .upload-text { font-size: 32rpx; color: #555; margin-bottom: 16rpx; } .upload-hint { font-size: 24rpx; color: #999; } .preview-section { width: 100%; } .image-comparison { display: flex; justify-content: space-around; margin-bottom: 50rpx; } .image-box { display: flex; flex-direction: column; align-items: center; } .image-label { font-size: 28rpx; color: #666; margin-bottom: 20rpx; } .preview-image { width: 320rpx; border-radius: 16rpx; box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.1); } button { width: 80%; margin-bottom: 30rpx; border-radius: 50rpx; font-size: 32rpx; } button[typeprimary] { background-color: #07c160; } .status-message { padding: 20rpx 40rpx; background-color: #e6f3ff; border-radius: 12rpx; margin: 30rpx 0; width: 80%; text-align: center; font-size: 28rpx; color: #0066cc; } .tips { margin-top: 60rpx; padding: 30rpx; background-color: #fff8e6; border-radius: 16rpx; width: 90%; font-size: 26rpx; color: #8a6d3b; line-height: 1.6; white-space: pre-line; }4. 实际应用与效果把前后端代码都部署好后这个小程序就可以跑起来了。实际用起来流程非常顺畅用户打开小程序看到一个简洁的上传页面。从手机相册选一张黑白的老照片比如一张几十年前的家庭合影。点击“开始AI上色”按钮照片开始上传界面显示“正在处理中...”。等待大约30-60秒具体时间取决于照片大小和服务器负载期间小程序会安静地在后台轮询结果。处理完成后页面会自动刷新原图旁边会出现一张色彩还原后的新图。人物的肤色、衣物的颜色、背景的景物都被AI智能地填充上了合理的色彩。用户可以左右对比如果满意可以长按彩色图片保存到手机。对于用户来说他们无需了解背后的技术细节只需要几次点击就能完成一次老照片的修复。这种“即拍即得”或“即传即得”的体验对于传播和分享非常有吸引力。你可以想象用户将修复后的彩色照片分享到朋友圈本身就是对小程序最好的推广。5. 总结通过这个项目我们完成了一个从想法到产品的完整闭环。前端微信小程序负责收集用户输入和展示结果提供了便捷的移动端入口后端SpringBoot服务作为中坚调度者处理文件、调用AI、管理任务状态而星图平台上的DeOldify模型则提供了核心的AI上色能力我们无需关心复杂的模型训练和部署通过API即可调用。整个过程涉及了小程序开发、文件上传、后端API编写、异步任务处理和第三方服务集成是一个很好的全栈实践。在实际部署时你还需要考虑一些工程化问题比如将图片存储到对象存储服务如OSS以节省服务器磁盘空间使用Redis来存储任务状态以保证服务重启后不丢失以及为API调用增加限流和降级策略以保证服务稳定性。这个案例的框架可以很容易地扩展到其他AI能力上比如人像卡通化、风格迁移、图像超分辨率等。只需要把后端的API调用换成其他模型的接口前端稍作调整一个新的小程序就又诞生了。希望这个实践能给你带来启发动手试试把你家的老照片也变成彩色的吧。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。