统计自己使用变焦镜头拍照的常用焦段(Python & Go)

Published: 2024-01-30

Tags: Golang Python

本文总阅读量

之前看到一个说法,如果一个变焦用户,想要买定焦镜头,但是不知道买哪个焦段,可以统计自己的拍摄照片,辅助判断。

然后看到一个博文,用 Python3 写代码进行的统计,于是我针对 RX100 黑卡相机做了调整,去掉了内置绘图,输出了 CSV,可以用在线工具导入展示,另外针对常用焦段,让近邻的焦段按比例汇总到常用焦段,图表上看的会更清晰。

示例图像

下载:demo-pics.zip

可以用于测试以下代码

焦距统计(Python 实现)

import exifread
import os

# 常用焦距
usual_focals = [16, 24, 28, 35, 50, 85, 105, 200, 300, 400]

def approximate_focal(focal):

    for usual in usual_focals:
        aggregation_range = int(usual / 8)
        if focal > usual - aggregation_range and focal < usual + aggregation_range:
            return usual

    return focal


def save_csv_format(dic):

    header = ','.join([str(item) for item in dic.keys()])
    content = ','.join([str(item) for item in dic.values()])

    with open("focal.csv", 'w', encoding='utf-8') as file:
        file.write(header + "\n")
        file.write(content)


def run():
    dic = {}
    for root, dirs, files in os.walk(".", topdown=False):
        for name in files:
            fname = os.path.join(root, name)
            if '.jpg' not in fname.lower():
                continue

            file = open(fname, "rb")
            tags = (exifread.process_file(file))
            file.close()

            # tags['EXIF FocalLength'] tags['Image Make'] tags['Image Model']
            if 'EXIF FocalLengthIn35mmFilm' in tags.keys():
                realfocal = int(str(tags['EXIF FocalLengthIn35mmFilm']))
                nearfocal = approximate_focal(realfocal)
            else:
                continue

            if nearfocal in dic.keys():
                dic[nearfocal] += 1
            else:
                dic[nearfocal] = 1

    dic = dict(sorted(dic.items(), key=lambda x:x[0]))
    print(dic)
    save_csv_format(dic)


if __name__ == "__main__":

    run()

我测试的图像大概有 3300 张照片,16G 大小,运行需要 33 秒

31.75s user 0.39s system 97% cpu 33.128 total

接下来使用 Golang 重构代码

焦距统计(Golang 实现)

package main

import (
    "fmt"
    "os"
    "path/filepath"
    "sort"
    "strconv"
    "strings"

    "github.com/dsoprea/go-exif/v3"
)

var usualFocal = []int{16, 24, 28, 35, 50, 85, 105, 200, 300, 400}

func approximateFocal(focal int) int {
    for _, usual := range usualFocal {
        aggregationRange := usual / 8
        if focal > usual-aggregationRange && focal < usual+aggregationRange {
            return usual
        }
    }
    return focal
}

func saveCsvFormat(dic map[int]int) {
    // 获取键,并按从小到大进行排序
    keys := make([]int, 0, len(dic))
    for key := range dic {
        keys = append(keys, key)
    }
    sort.Ints(keys)

    // 生成表头
    header := make([]string, 0, len(dic))
    for _, key := range keys {
        header = append(header, strconv.Itoa(key))
    }
    headerLine := strings.Join(header, ",")

    // 生成内容
    content := make([]string, 0, len(dic))
    for _, key := range keys {
        content = append(content, strconv.Itoa(dic[key]))
    }
    contentLine := strings.Join(content, ",")

    file, err := os.Create("focal.csv")
    if err != nil {
        fmt.Println(err)
    }
    defer file.Close()

    _, err = file.WriteString(headerLine + "\n" + contentLine)
    if err != nil {
        fmt.Println(err)
    }
}

func main() {

    folderPath := "."
    var dic = make(map[int]int)

    _ = filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }

        if !(info.Mode().IsRegular() && strings.ToLower(filepath.Ext(path)) == ".jpg") {
            return nil
        }

        exifData, err := exif.SearchFileAndExtractExif(path)
        if err != nil {
            return err
        }

        exifTags, _, err := exif.GetFlatExifData(exifData, nil)

        for _, item := range exifTags {
            if strings.Contains(item.TagName, "FocalLengthIn35mmFilm") {
                focalLength := int(item.Value.([]uint16)[0])
                focalLength = approximateFocal(focalLength)

                _, ok := dic[focalLength]
                if ok {
                    dic[focalLength] += 1
                } else {
                    dic[focalLength] = 1
                }
            }
        }
        return nil
    })
    fmt.Println(dic)
    saveCsvFormat(dic)
}

编译运行

11.19s user 5.45s system 92% cpu 17.889 total

时间相比 Python 版本节省了一半,观察磁盘吞吐速率,Golang 基本上将磁盘的读取速度吃满了

使用工具展示图表

我使用的是这个在线网页:https://charts.livegap.com/app.php?lan=zh&gallery=line

没什么特别的原因,它支持导入 csv 数据同时不需要登录,就用它用来测试了。

从折线图可以看到我在使用 RX100M1(等效 35 MM 格式焦距 28-100 mm) 时,广角 28mm 使用的最多,其次是长焦 100mm 最多,35mm 和 50mm 差不多,85mm 略少于两者。

那么问题来了,我第一颗定焦镜头买什么焦段呢? 🤔

参考