说实话,我第一次接到这个需求的时候,差点以为自己听错了,一个朋友跑来问我:“能不能用Go语言做个程序,生成一个365朵鲜玫瑰花束组成心形的视频?”我当时的第一反应是——这玩意儿不是该用AE或者PR做吗?但转念一想,用代码生成视频这事儿,其实挺有意思的,尤其当每一朵花都代表着一年中的某一天,用代码把他们排列成心形,再配合上动态效果,那种浪漫感就完全不一样了。
后来我花了大概两周时间,把这事儿给整明白了,今天就把这个过程拆开揉碎了,跟大家聊聊。
为什么用Go语言来做视频生成?
很多人觉得Go语言是搞后端、搞微服务的,跟视频处理八竿子打不着,但恰恰相反,Go在图像处理和视频帧合成方面,其实有着不小的优势。
Go的优势体现在这么几个地方:
- 并发处理能力强:生成365朵花的每一帧,可以并行计算,效率翻倍
- 内存管理没那么操蛋:不用像C++那样手动释放资源
- 标准库还算够用:image包、io包直接就能上手
- 编译速度快:改完代码跑一下,几秒钟就能看到效果
我试过用Python做类似的事情,但处理大尺寸视频帧的时候,GIL锁的问题确实让人头大,Go就没这毛病。
核心思路:怎么用代码表达365朵花的心形?
先说说整体逻辑,我们要生成一个视频,视频里有一个由365朵玫瑰花组成的心形,每一朵花代表一年中的一天,而且每一朵都可以单独控制——颜色、大小、朝向、甚至动画。
心形曲线的数学表达
心形曲线这事儿,网上公式一大堆,我最后用的是参数方程:
// 心形曲线参数方程
func heartX(t float64) float64 {
return 16 * math.Pow(math.Sin(t), 3)
}
func heartY(t float64) float64 {
return 13*math.Cos(t) - 5*math.Cos(2*t) - 2*math.Cos(3*t) - math.Cos(4*t)
}
这个公式生成出来的心形,饱满程度刚刚好。
365朵花的排列逻辑
问题来了:365朵花,怎么均匀分布在心形曲线上?直接等间距取角度不行,因为曲线在有些地方密集,有些地方稀疏,我加了个重采样逻辑,把弧长算出来,然后按弧长均匀分布。
具体的做法是:

- 把角度t从0到2π分成5000个点
- 计算每个点的坐标和相邻点之间的弧长
- 算出总弧长,然后按365份等分
- 找到每个等分点对应的t值,算出坐标
type Flower struct {
X, Y float64
Angle float64 // 旋转角度,让花朵朝向心形中心
Size float64 // 花的大小,可以动态变化
Color color.RGBA
DayIndex int // 代表第几天
}
每个Flower结构体保存了一朵花的完整信息。
视频生成的核心代码实现
这部分我分成了几个模块,
模块1:玫瑰花的绘制
画一朵玫瑰花的代码,说实话是最考验耐心的,我试了三种方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 用椭圆堆叠模拟花瓣 | 代码可控,每朵花不同 | 效果不够真实 |
| 用贝塞尔曲线描花形 | 形状灵活 | 计算量大,每帧重新算 |
| 混合方案(最终选择) | 兼顾速度和效果 | 代码稍微复杂一点 |
最终我选了混合方案——先用贝塞尔曲线画5层花瓣,每层6片花瓣,然后用渐变填充增加立体感,花瓣的颜色从中心的深红渐变到边缘的鲜红,再配上黄色的花蕊。
画花瓣的核心逻辑:
func drawPetal(img *image.RGBA, centerX, centerY, size float64, angle float64, color color.RGBA) {
// 用3个贝塞尔点控制花瓣形状
p1 := point{centerX, centerY + size*0.5}
p2 := point{centerX + size*0.3, centerY - size*0.2}
p3 := point{centerX, centerY - size*0.6}
p4 := point{centerX - size*0.3, centerY - size*0.2}
// 旋转角度并绘制
// 实际代码里用了image/draw和贝塞尔插值
}
说真的,这段代码我改了好几版,一开始画出来的花瓣像土豆,后来一点点调参数,终于看着像朵花了。
模块2:心形排布与帧动画
画好单朵花之后,把它们排成心形,但只是静态的心形太无聊了,我们要的是视频,所以得动起来。
我设计了三种动画效果:
- 绽放动画:花朵从小到大,从花苞到盛开
- 呼吸效果:整体心跳一样的缩放
- 流光效果:颜色波浪一样扫过心形
动画的实现方式是用时间线控制每一帧的参数,比如第0帧到第50帧,花朵从0.3倍大小逐渐增长到1.0倍;第50帧之后开始呼吸效果。
func generateFrame(flowers []Flower, frameIdx int, width, height int) *image.RGBA {
img := image.NewRGBA(image.Rect(0, 0, width, height))
// 背景填充
bgColor := interpolateColor(color.RGBA{20, 20, 30, 255}, color.RGBA{40, 10, 20, 255}, float64(frameIdx)/240)
drawBackground(img, bgColor)
// 绘制每一朵花
for i, flower := range flowers {
// 根据帧索引计算动态参数
t := float64(frameIdx) / 240
blossomFactor := math.Min(1.0, float64(frameIdx+flower.DayIndex)%120/60)
dynamicSize := flower.Size * (0.3 + 0.7*blossomFactor)
dynamicColor := adjustColorBrightness(flower.Color, 0.5+0.5*math.Sin(float64(frameIdx)*0.05+float64(i)*0.1))
drawFlowerAt(img, flower.X, flower.Y, dynamicSize, flower.Angle, dynamicColor)
}
return img
}
模块3:编码成视频
帧画好了,得合成视频,Go里面常用的方案有这么几种:
- 用FFmpeg的pipe:外部调用FFmpeg,一帧一帧喂进去
- 用goav库:直接包装FFmpeg的API
- 用gocv:OpenCV的Go绑定
- 自己写MP4编码:不建议,太复杂了
我选了方案1——用os/exec调用FFmpeg,简单粗暴,而且FFmpeg的编码质量有保证。
func encodeVideo(frameDir string, outputPath string, fps int) error {
cmd := exec.Command("ffmpeg",
"-y",
"-framerate", strconv.Itoa(fps),
"-i", filepath.Join(frameDir, "frame_%05d.png"),
"-c:v", "libx264",
"-pix_fmt", "yuv420p",
"-crf", "18",
outputPath,
)
return cmd.Run()
}
这里有个坑提醒一下:直接喂图片给FFmpeg,如果图片尺寸是1920x1080,那么crf值18出来的文件大概50MB左右,画质很好,但如果视频要放到抖音或者视频号,建议用1920x1080,帧率24fps就够了,再高看不出区别,文件还巨大。
优化与踩坑记录
性能优化:从半小时到3分钟
最开始跑一版,生成10秒视频(240帧),花了整整35分钟。这显然不行。
我做了几项优化:
优化1:并发渲染帧
Go的goroutine在这里派上大用场了,每一帧的渲染是独立的,直接用worker pool来并行处理。
func renderFramesConcurrently(numFrames int, workers int) {
frameChan := make(chan int, numFrames)
var wg sync.WaitGroup
for w := 0; w < workers; w++ {
wg.Add(1)
go func() {
defer wg.Done()
for frameIdx := range frameChan {
img := generateFrame(flowers, frameIdx, 1920, 1080)
saveFrame(img, frameIdx)
}
}()
}
for i := 0; i < numFrames; i++ {
frameChan <- i
}
close(frameChan)
wg.Wait()
}
设8个worker,直接把时间压到了7分钟。
优化2:花朵预渲染
每朵花其实可以预渲染成小图片,然后用image/draw直接贴到主画布上,不用每帧重新画花瓣,速度快了一倍多。
优化3:跳过不必要的光晕
一开始我每朵花都加了光晕效果,但光晕计算太费时间,后来改成只在周围的花朵上做光晕,心形内部的花朵保持清晰,从7分钟降到了4分钟。
内存问题:不要一次性加载所有帧
最开始我把所有帧都存在内存里,然后批量写出去,结果一跑,内存飙到8个G,后来改成逐帧写入磁盘,问题解决。
// 错误做法
var frames []*image.RGBA
for i := 0; i < 240; i++ {
frames = append(frames, generateFrame(...)) // 内存爆炸
}
// 正确做法
for i := 0; i < 240; i++ {
img := generateFrame(...)
saveFrameToDisk(img, i) // 直接写文件
}
最终的视频长什么样
跑完所有代码之后,你得到一个MP4文件,打开来看——
灰黑色的背景上,一个由365朵鲜红玫瑰组成的心形,从中心开始一朵一朵绽放,像是有生命一样,每朵花的花瓣微微颤动,心形整体在缓慢呼吸,颜色从深红到亮红像波浪一样流转。说实话,第一次看到成品的时候,我自己都愣了。 虽然代码是我写的,但看到它活起来,那种感觉真的很奇妙。
时间长度大概是10秒到30秒可调,我一般做15秒,配上《千与千寻》的钢琴版背景音乐(这个得后期配,Go做不了音频合成我还没搞)。
用这个程序能做什么?
- 表白视频:心形花束视频,配上文字,谁用谁知道
- 纪念日礼物:365天的每一天,每一天都是心形的一部分
- 求婚创意:把两个人的合影嵌入到心形中心
- 商业用途:花店的宣传视频,婚纱摄影的素材
我还扩展了一个版本,支持自定义每朵花的颜色,比如1月到3月用粉色,4月到6月用红色,7月到9月用橙色,10月到12月用白色,这样心形上就出现了春夏秋冬的渐变。
一些想说的话
写这篇文章的时候,我正坐在咖啡馆里,旁边一桌的情侣在看手机上的短视频,我瞄了一眼,是一个用AE做的花束视频,我在想,如果那个视频是用我的Go代码生成的,会怎么样?
技术这东西,有时候看起来冷冰冰的。但当你用代码创造美的时候,它就有了温度。 365朵花,每一朵都对应着一年中的某一天,当你把这个视频送给某个人,其实是在说:你的每一天,都被我记住,且都美丽如花。
代码我已经开源在GitHub上了,搜“go-365-heart-flowers”就能找到,如果你也试着跑一遍,记得调低花朵的透明度参数,或者干脆换一个你喜欢的颜色——毕竟,真正的浪漫,从来都是定制化的。
嗯,再说下去就跑题了,文章到这里就差不多了,谢谢你能看到这里。
本文来自作者[kyadmin]投稿,不代表be365立场,如若转载,请注明出处:http://www.hljbesthome.com/jiankang/968.html
评论列表(4条)
我是be365的签约作者“kyadmin”!
希望本篇文章《用Go语言打造365朵鲜玫瑰花束心形视频,从零开始的浪漫编程之旅》能对你有所帮助!
本站[be365]内容主要涵盖:be365,be365网站,be365网址,ball365体育,Be365排名
本文概览:说实话,我第一次接到这个需求的时候,差点以为自己听错了,一个朋友跑来问我:“能不能用Go语言做个程序,生成一个365朵鲜玫瑰花束组成心形...