前言
作为 Go 后端的唯一妹纸实习生,Boss 听说我之前跳过舞,实习第一天让我跳个程序员版本科目三,帮忙宣传一波,00 后整顿职场不能怂,被我义正严辞地拒绝了。
没想到过了一个小时,老板发了个红包。
人间疾苦,有钱无阻,老板,你看人真准。自己跳是不可能的,咱来个“曲线救国”让 Excel 替我跳一个科目三。
如何实现让 Excel 跳科目三呢?经过一番秃头研究👨🦲,想到视频都是由连续的图片组成,想要实现在 Excel 中跳舞那就需要多张连续的图片,只要找一个科目三舞蹈视频通过转换为连续的图片并将图片导入 Excel ,然后使用 Chromeless 截取个几百张图最后组装就能生成舞蹈视频啦。
但是只插入图片未免太过无趣,网上搜罗一圈发现图片还能转 ASCII 字符集,且导入 Excel 方案可行,这就开干!
视频转图片
首先是保存一个科目三的舞蹈视频,这里从各大视频网站中可以获取。从 Chrome 应用商店里白嫖一个下载插件,再白嫖一个科目三视频。
最后,使用 ffmpeg 这个强大的视频流处理工具来获取指定帧数的图片。
官网下载地址:https://ffmpeg.org/download.html
从目标.mp4 视频文件中提取图像帧,每秒提取 15 帧,输出的图像文件名格式为三位数字.jpg
ffmpeg -i 目标.mp4 -r 15 %3d.jpg
运行结果如下:
图片转 ASCII 字符集
重头戏之一来了,在线的转换工具倒是有不少,效果也不错👇
在线转换网站地址:https://neucrack.com/tools/ascii
但是一秒的视频至少 15 张图片,十秒就是 150 张一个一个手动转换和手动导入 Excel 会夭寿的。
作为计算机专业出身,代码编写少不了,使用 Go 语言实现以上功能,流程图如下:
这里使用广泛使用的 "github.com/fogleman/gg" 和 "github.com/nfnt/resize" 图形处理库来进行图片的缩放以及像素获取
func processPixel(context *gg.Context, file *excelize.File, h, w int) {
//获取像素点颜色
pixelColor := context.Image().At(w, h)
r, g, b, _ := pixelColor.RGBA()
//获取像素点亮度值
pixelValue, _, _ := color.RGBToYCbCr(uint8(r/256), uint8(g/256), uint8(b/256))
asciiIndex := int((float64(pixelValue) / 255) * float64(len(asciiChars)-1))
asciiChar := string(asciiChars[asciiIndex])
weight := asciiWeights[asciiIndex]
fontColor := fmt.Sprintf("%02X%02X%02X", int(r/256), int(g/256), int(b/256))
result := strings.Repeat(asciiChar, int(weight))
cell := fmt.Sprintf("%s%d", columnIndexToExcelName(w), h+1)
file.SetCellValue(sheetName, cell, result)
style, _ := file.NewStyle(&excelize.Style{
Font: &excelize.Font{
Color: fontColor, //字体颜色
},
Border: []excelize.Border{}, //空切片无边框
})
file.SetCellStyle(sheetName, cell, cell, style)
}
映射方法:
在 RGB 色彩模型中,亮度的取值通常与颜色的亮度成正比。在这里,pixelValue 是从图像中提取的像素的亮度值。对于 RGB 色彩模型,可以通过以下公式将 RGB 值转换为亮度值:亮度值=0.299×R+0.587×G+0.114×B
这里,R、G、B 分别是像素的红、绿、蓝分量。这个公式是一种常见的计算灰度值的方法,其中每个颜色分量的权重反映了人眼对不同颜色的感知。在这种情况下,亮度值的范围通常是 [0, 255],其中 0 表示黑色,255 表示白色。
通常情况下,亮度值越大,表示像素的颜色越接近白色,亮度越小,表示颜色越接近黑色。在上述代码中,pixelValue 的值被归一化到范围 [0, 1],用于确定在 ASCII 字符集中选择哪个字符。
例如,根据自定义的 ASCII 字符集和权重:
-asciiChars="@%#*+=-" -weights="15,10,7,5,3,2,1"
亮度越大的值归一化后越接近 1,对应的字符索引越大,亮度越小的值归一化后越接近 0,对应的字符索引越小,255 白色亮度对应索引 index=len(asciiChars)-1 即转换后显示 1 个-字符,0 亮度黑色对应索引 index=0 即转换后显示 15 个@字符。
ASCII 字符集导入 Excel
这里使用 github.com/xuri/excelize/v2 库来进行 Excel 处理,进行单元格行列高列宽设置,单元格内容填充,并行处理像素点,最后保存 Excel 文件。
func imageToASCII(imagePath, outputExcel string) {
context, err := openAndResizeImage(imagePath, scaleFactor)
if err != nil {
fmt.Println(err)
return
}
//获取像素大小
imgWidth, imgHeight := context.Image().Bounds().Dx(), context.Image().Bounds().Dy()
file, _, err := createExcelFile()
if err != nil {
fmt.Println(err)
return
}
//设置单元格大小
setColumnWidths(file, imgWidth)
setRowHeights(file, imgHeight)
for h := 0; h < imgHeight; h++ {
wg.Add(1)
//并行处理每行像素点
go func(h int) {
defer wg.Done()
for w := 0; w < imgWidth; w++ {
processPixel(context, file, h, w)
}
}(h)
}
// 等待所有 goroutine 完成
wg.Wait()
// 保存 Excel 文件
if err := file.SaveAs(outputExcel); err != nil {
fmt.Println("Error saving Excel file:", err)
} else {
fmt.Println("Excel file saved")
}
}
运行示例
-
以默认设置启动
-
通过命令行参数自定义运行
参数解释如下图:go run ascii.go -h
go run ascii.go -asciiChars="@%#*+=-:." -weights="15,10,7,5,3,2,1,0.5,0.1"
图片转视频
用程序将 excel 导入到石墨文档,在使用 chromedp 截图,最终使用 ffmpeg 转为每秒 15 帧的视频,然后播放,大功告成!
ffmpeg -r 15 -pattern_type glob -i '截屏*.png' video1.mp4
播放:ffplay video1.mp4
总结
到此,介绍了利用 Go 语言和一些开源库实现的一个有趣项目,通过将图片转换为 ASCII 字符,并将结果保存到 Excel 文件中,最终实现 Excel 版科目三视频。支持自定义字符集和权重以及图片缩放大小来实现不同的视觉效果。同时项目的不足之处在于处理大型图片时性能可能较差,可以考虑优化算法或者优化并行处理,后续也可以支持选择不同的图片转换方式,例如像素图等。希望通过分享这个项目,能够激发更多人对图形处理和代码创意的兴趣。
最后附上参考资料以及完整的源码地址:
-
完整的源码:https://github.com/shimo-open/ascii-to-excel
-
fogleman/gg官方文档:https://pkg.go.dev/github.com/fogleman/gg
-
github地址:https://github.com/qax-os/excelize
-
ffmpeg官网下载:https://ffmpeg.org/download.html