add face recognition

This commit is contained in:
2025-01-22 10:36:28 +08:00
parent eab806fb9b
commit c6af9a0461
47 changed files with 3621 additions and 454 deletions

View File

@@ -0,0 +1,5 @@
package constant
const (
FaceTypeSample = "sample"
)

View File

@@ -13,3 +13,7 @@ const (
const (
SystemApiNoncePrefix = "system:nonce:"
)
const (
FaceSamplePrefix = "face:sample:"
)

View File

@@ -0,0 +1,24 @@
package face_recognizer
import (
"github.com/Kagami/go-face"
"os"
"path/filepath"
)
// NewFaceRecognition creates a new instance of FaceRecognition
func NewFaceRecognition() *face.Recognizer {
dir, err := os.Getwd()
if err != nil {
panic(err)
return nil
}
modelDir := filepath.Join(dir, "/resources/models/face")
rec, err := face.NewRecognizer(modelDir)
if err != nil {
panic(err)
return nil
}
//defer rec.Close()
return rec
}

84
common/gao_map/amap.go Normal file
View File

@@ -0,0 +1,84 @@
package gao_map
import (
"errors"
"github.com/duke-git/lancet/v2/cryptor"
"github.com/duke-git/lancet/v2/maputil"
"github.com/duke-git/lancet/v2/netutil"
"github.com/duke-git/lancet/v2/slice"
"net/http"
"net/url"
"strings"
)
type AmapClient struct {
key string
secret string
Location *Location
Direction *Direction
Place *Place
Weather *Weather
}
func NewAmapClient(key, secret string) *AmapClient {
amap := &AmapClient{key: key, secret: secret}
amap.Location = &Location{client: amap}
amap.Direction = &Direction{client: amap}
amap.Place = &Place{client: amap}
amap.Weather = &Weather{client: amap}
return amap
}
func (a *AmapClient) DoRequest(url, method string, params url.Values) (resp *http.Response, err error) {
params.Set("key", a.key)
if a.secret != "" {
params.Set("sig", makeSign(a, &params))
}
switch strings.ToUpper(method) {
case "GET":
resp, err = netutil.HttpGet(url, map[string]string{}, params)
case "POST":
resp, err = netutil.HttpPost(url, map[string]string{}, params)
default:
err = errors.New("unknown request method")
}
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, errors.New("amap request error: " + resp.Status)
}
return
}
type Response struct {
Status string `json:"status"`
Info string `json:"info"`
Infocode string `json:"Infocode"`
}
func makeSign(client *AmapClient, values *url.Values) string {
keys := maputil.Keys(*values)
slice.SortBy(keys, func(a, b string) bool {
return a < b
})
data := slice.Map(keys, func(index int, item string) string {
return item + "=" + values.Get(item)
})
str := slice.Join(data, "&") + client.secret
return cryptor.Md5String(str)
}
func checkResponse(response Response) error {
if response.Status != "1" || response.Infocode != "10000" {
return errors.New("amap response error: " + response.Info)
}
return nil
}

105
common/gao_map/beans.go Normal file
View File

@@ -0,0 +1,105 @@
package gao_map
type WeatherResponse struct {
Response
Lives []struct {
Adcode string `json:"adcode"`
City string `json:"city"`
Province string `json:"province"`
Weather string `json:"weather"`
Temperature string `json:"temperature"`
WindDirection string `json:"winddirection"`
WindPower string `json:"windpower"`
Humidity string `json:"humidity"`
ReportTime string `json:"reporttime"`
TemperatureFloat string `json:"temperature_float"`
HumidityFloat string `json:"humidity_float"`
} `json:"lives"`
Forecast []struct {
City string `json:"city"`
Adcode string `json:"adcode"`
Province string `json:"province"`
ReportTime string `json:"reporttime"`
Casts []struct {
Date string `json:"date"`
Week string `json:"week"`
DayWeather string `json:"dayweather"`
NightWeather string `json:"nightweather"`
DayTemp string `json:"daytemp"`
NightTemp string `json:"nighttemp"`
DayWind string `json:"daywind"`
NightWind string `json:"nightwind"`
DayPower string `json:"daypower"`
NightPower string `json:"nightpower"`
} `json:"casts"`
} `json:"forecast"`
}
type IpResponse struct {
Response
Province string `json:"province"`
City any `json:"city"`
Adcode string `json:"adcode"`
Rectangle string `json:"rectangle"`
}
type District struct {
Citycode any `json:"citycode"`
Adcode string `json:"adcode"`
Name string `json:"name"`
Center string `json:"center"`
Level string `json:"level"`
Districts []*District `json:"districts"`
}
type AllDistrictResponse struct {
Response
Count int `json:"count,string"`
Districts []*District
}
type Geo struct {
FormattedAddress string `json:"formatted_address"`
Country string `json:"country"`
Province string `json:"province"`
Citycode string `json:"citycode"`
City any `json:"city"`
Adcode string `json:"adcode"`
Location string `json:"location"`
Level string `json:"level"`
}
type GeoResponse struct {
Response
Count string `json:"count"`
GeoCodes []Geo `json:"geocodes"`
}
type ReGeoResponse struct {
Response
ReGeoCode struct {
FormattedAddress string `json:"formatted_address"`
AddressComponent struct {
City any `json:"city"`
Country string `json:"country"`
Province string `json:"province"`
Citycode string `json:"citycode"`
District any `json:"district"`
Adcode string `json:"adcode"`
Township string `json:"township"`
Towncode string `json:"towncode"`
} `json:"addressComponent"`
} `json:"regeocode"`
}
type ReGeoRequest struct {
Location string
//PoiType string
Radius int
//Extensions string
//RoadLevel int
}

117
common/gao_map/direction.go Normal file
View File

@@ -0,0 +1,117 @@
package gao_map
import (
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/netutil"
"github.com/duke-git/lancet/v2/strutil"
"net/url"
"strings"
)
const drivingUrl = "https://restapi.amap.com/v5/direction/driving" //驾车
type Direction struct {
client *AmapClient
}
type DrivingRequest struct {
Origin string
Destination string
Strategy int
Waypoints []string
ShowFields string
}
type BicyclingRequest struct {
Origin string
Destination string
ShowFields string
}
type Route struct {
Origin string `json:"origin"`
Destination string `json:"destination"`
Paths []struct {
Distance float64 `json:"distance,string"`
Steps []struct {
Cost struct {
Duration float64 `json:"duration,string"`
Tolls float64 `json:"tolls,string"`
TollsDistance float64 `json:"tolls_distance,string"`
} `json:"cost"`
Cities []struct {
Adcode string `json:"adcode"`
Citycode string `json:"citycode"`
City string `json:"city"`
} `json:"cities"`
Polyline string `json:"polyline"`
} `json:"steps"`
} `json:"paths"`
}
type DrivingResponse struct {
Response
Count int `json:"count,string"`
Route Route `json:"route"`
}
type BicyclingResponse struct {
Response
Count int `json:"count,string"`
Route Route `json:"route"`
}
func (d *Direction) Bicycling(request *BicyclingRequest) (*BicyclingResponse, error) {
val := url.Values{}
val.Set("origin", request.Origin)
val.Set("destination", request.Destination)
if !strutil.IsBlank(request.ShowFields) {
val.Set("show_fields", request.ShowFields)
}
resp, err := d.client.DoRequest(drivingUrl, "POST", val)
if err != nil {
return nil, err
}
var data BicyclingResponse
if err = netutil.ParseHttpResponse(resp, &data); err != nil {
return nil, err
} else if err = checkResponse(data.Response); err != nil {
return nil, err
}
return &data, err
}
func (d *Direction) Driving(request *DrivingRequest) (*DrivingResponse, error) {
val := url.Values{}
val.Set("origin", request.Origin)
val.Set("destination", request.Destination)
val.Set("strategy", convertor.ToString(request.Strategy))
if len(request.Waypoints) > 0 {
val.Set("waypoints", strings.Join(request.Waypoints, ";"))
}
if !strutil.IsBlank(request.ShowFields) {
val.Set("show_fields", request.ShowFields)
}
resp, err := d.client.DoRequest(drivingUrl, "POST", val)
if err != nil {
return nil, err
}
var data DrivingResponse
if err = netutil.ParseHttpResponse(resp, &data); err != nil {
return nil, err
} else if err = checkResponse(data.Response); err != nil {
return nil, err
}
return &data, err
}

109
common/gao_map/location.go Normal file
View File

@@ -0,0 +1,109 @@
package gao_map
import (
"errors"
"github.com/duke-git/lancet/v2/condition"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/netutil"
"github.com/duke-git/lancet/v2/validator"
"net/url"
)
const (
ipUrl = "https://restapi.amap.com/v3/ip"
geoUrl = "https://restapi.amap.com/v3/geocode/geo"
regeoUrl = "https://restapi.amap.com/v3/geocode/regeo"
zoneUrl = "https://restapi.amap.com/v3/config/district"
)
type Location struct {
client *AmapClient
}
func (l *Location) ChinaDistricts() ([]*District, error) {
val := url.Values{}
val.Set("keywords", "中国")
val.Set("subdistrict", "3")
resp, err := l.client.DoRequest(zoneUrl, "GET", val)
if err != nil {
return nil, err
}
var data AllDistrictResponse
if err = netutil.ParseHttpResponse(resp, &data); err != nil {
return nil, err
} else if err = checkResponse(data.Response); err != nil {
return nil, err
} else if data.Count <= 0 {
return nil, errors.New("no record")
}
return data.Districts[0].Districts, err
}
func (l *Location) IpLocation(ip string) (*IpResponse, error) {
val := url.Values{}
val.Set("ip", ip)
resp, err := l.client.DoRequest(ipUrl, "GET", val)
if err != nil {
return nil, err
}
var data IpResponse
if err = netutil.ParseHttpResponse(resp, &data); err != nil {
return nil, err
} else if err = checkResponse(data.Response); err != nil {
return nil, err
}
return &data, nil
}
func (l *Location) ReGeo(request *ReGeoRequest) (*ReGeoResponse, error) {
if validator.IsEmptyString(request.Location) {
return nil, errors.New("location can't empty")
}
val := url.Values{}
val.Set("location", request.Location)
//if request.PoiType != "" {
// val.Set("poitype", request.PoiType)
//}
//val.Set("extensions", condition.Ternary(request.Extensions == "", "base", request.Extensions))
val.Set("radius", convertor.ToString(condition.Ternary(request.Radius == 0, 1000, request.Radius)))
//val.Set("roadlevel", convertor.ToString(request.RoadLevel))
resp, err := l.client.DoRequest(regeoUrl, "GET", val)
var data ReGeoResponse
if err = netutil.ParseHttpResponse(resp, &data); err != nil {
return nil, err
} else if err = checkResponse(data.Response); err != nil {
return nil, err
}
return &data, nil
}
func (l *Location) Geo(address, city string) (*GeoResponse, error) {
val := url.Values{}
val.Set("address", address)
if city != "" {
val.Set("city", city)
}
resp, err := l.client.DoRequest(geoUrl, "GET", val)
var data GeoResponse
if err = netutil.ParseHttpResponse(resp, &data); err != nil {
return nil, err
} else if err = checkResponse(data.Response); err != nil {
return nil, err
}
return &data, nil
}

View File

@@ -0,0 +1,50 @@
package gao_map
import (
"fmt"
"testing"
)
const key = ""
const secret = ""
func TestDistricts(t *testing.T) {
client := NewAmapClient(key, secret)
resp, err := client.Location.ChinaDistricts()
if err != nil {
t.Fatal(err)
}
fmt.Println(resp)
}
func TestIp(t *testing.T) {
client := NewAmapClient(key, secret)
resp, err := client.Location.IpLocation("121.224.33.99")
if err != nil {
t.Fatal(err)
}
fmt.Println(*resp)
}
func TestGeo(t *testing.T) {
client := NewAmapClient(key, secret)
resp, err := client.Location.Geo("乐东公园一品墅", "")
if err != nil {
t.Fatal(err)
}
fmt.Println(*resp)
}
func TestReGeo(t *testing.T) {
client := NewAmapClient(key, secret)
request := ReGeoRequest{Location: "118.567824,29.306175"}
resp, err := client.Location.ReGeo(&request)
if err != nil {
t.Fatal(err)
}
fmt.Println(*resp)
}

82
common/gao_map/place.go Normal file
View File

@@ -0,0 +1,82 @@
package gao_map
import (
"github.com/duke-git/lancet/v2/condition"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/netutil"
"github.com/duke-git/lancet/v2/strutil"
"net/url"
)
const placeUrl = "https://restapi.amap.com/v5/place/text" //驾车
type Place struct {
client *AmapClient
}
type PlaceRequest struct {
Keywords string
Types string
Region string
CityLimit bool
PageSize int
PageNum int
ShowFields string
}
type PlaceResponse struct {
Response
Count int `json:"count,string"`
Pois []struct {
Address string `json:"address"`
Adcode string `json:"adcode"`
Type string `json:"type"`
Typecode string `json:"typecode"`
CityCode string `json:"citycode"`
Name string `json:"name"`
Location string `json:"location"`
Id string `json:"id"`
Business struct {
Tel string `json:"tel"`
Cost string `json:"cost"`
Tag string `json:"tag"`
OpentimeToday string `json:"opentime_today"`
OpentimeWeek string `json:"opentime_week"`
} `json:"business"`
Photos []struct {
Title string `json:"title"`
Url string `json:"url"`
} `json:"photos"`
} `json:"pois"`
}
func (d *Place) Search(request *PlaceRequest) (*PlaceResponse, error) {
val := url.Values{}
val.Set("keywords", request.Keywords)
val.Set("types", request.Types)
if !strutil.IsBlank(request.Region) {
val.Set("region", request.Region)
}
val.Set("city_limit", condition.TernaryOperator(request.CityLimit, "1", "0"))
val.Set("page_size", convertor.ToString(condition.TernaryOperator(request.PageSize > 0, request.PageSize, 25)))
val.Set("page_num", convertor.ToString(condition.TernaryOperator(request.PageNum > 0, request.PageNum, 1)))
if !strutil.IsBlank(request.ShowFields) {
val.Set("show_fields", request.ShowFields)
}
resp, err := d.client.DoRequest(placeUrl, "GET", val)
if err != nil {
return nil, err
}
var data PlaceResponse
if err = netutil.ParseHttpResponse(resp, &data); err != nil {
return nil, err
} else if err = checkResponse(data.Response); err != nil {
return nil, err
}
return &data, err
}

32
common/gao_map/weather.go Normal file
View File

@@ -0,0 +1,32 @@
package gao_map
import (
"github.com/duke-git/lancet/v2/netutil"
"net/url"
)
const weatherInfoUrl = "https://restapi.amap.com/v3/weather/weatherInfo"
type Weather struct {
client *AmapClient
}
func (w *Weather) Info(adcode, extensions string) (*WeatherResponse, error) {
val := url.Values{}
val.Set("city", adcode)
resp, err := w.client.DoRequest(weatherInfoUrl, "GET", val)
if err != nil {
return nil, err
}
var data WeatherResponse
if err = netutil.ParseHttpResponse(resp, &data); err != nil {
return nil, err
} else if err = checkResponse(data.Response); err != nil {
return nil, err
}
return &data, nil
}

View File

@@ -1,7 +1,6 @@
package ip2region
import (
"fmt"
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
"os"
"path/filepath"
@@ -13,7 +12,6 @@ func NewIP2Region() *xdb.Searcher {
if err != nil {
panic(err)
}
fmt.Println(cwd)
dbPath := filepath.Join(cwd, "resources/ip2region", "ip2region.xdb")
cBuff, err := xdb.LoadContentFromFile(dbPath)
if err != nil {

View File

@@ -1,65 +0,0 @@
package utils
import (
"crypto"
"encoding/hex"
"fmt"
"hash"
"io"
"os"
)
// SupportedHashFuncs 定义支持的哈希函数类型
var SupportedHashFuncs = map[string]func() hash.Hash{
"md5": crypto.MD5.New,
"sha1": crypto.SHA1.New,
"sha256": crypto.SHA256.New,
"sha512": crypto.SHA512.New,
}
// CalculateFileHash 根据指定的哈希算法计算文件的哈希值
func CalculateFileHash(filePath string, algorithm string) (string, error) {
// 获取对应的哈希函数
hashFunc, exists := SupportedHashFuncs[algorithm]
if !exists {
return "", fmt.Errorf("unsupported hash algorithm: %s", algorithm)
}
// 打开文件
file, err := os.Open(filePath)
if err != nil {
return "", fmt.Errorf("failed to open file: %w", err)
}
defer file.Close()
// 创建哈希对象
h := hashFunc()
// 计算哈希值
if _, err := io.Copy(h, file); err != nil {
return "", fmt.Errorf("failed to calculate hash: %w", err)
}
// 返回哈希值的十六进制字符串
return hex.EncodeToString(h.Sum(nil)), nil
}
// CalculateStreamHash 计算输入流的哈希值
func CalculateStreamHash(reader io.Reader, algorithm string) (string, error) {
// 获取对应的哈希函数
hashFunc, exists := SupportedHashFuncs[algorithm]
if !exists {
return "", fmt.Errorf("unsupported hash algorithm: %s", algorithm)
}
// 创建哈希对象
h := hashFunc()
// 从输入流计算哈希值
if _, err := io.Copy(h, reader); err != nil {
return "", fmt.Errorf("failed to calculate hash: %w", err)
}
// 返回哈希值的十六进制字符串
return hex.EncodeToString(h.Sum(nil)), nil
}

View File

@@ -0,0 +1,24 @@
package utils
import (
"github.com/corona10/goimagehash"
"image"
)
// CalculatePerceptualHash 计算感知哈希
func CalculatePerceptualHash(img image.Image) (string, error) {
hash, err := goimagehash.PerceptionHash(img)
if err != nil {
return "", err
}
return hash.ToString(), nil
}
// CalculateHash 计算平均哈希
func CalculateHash(img image.Image) (uint64, error) {
hash, err := goimagehash.AverageHash(img)
if err != nil {
return 0, err
}
return hash.GetHash(), nil
}

View File

@@ -1,8 +1,13 @@
package utils
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"image"
_ "image/jpeg" // 引入 jpeg 解码器
_ "image/png" // 引入 png 解码器
"io"
"regexp"
"strings"
@@ -88,3 +93,25 @@ func Base64ToBytes(base64Str string) ([]byte, error) {
}
return data, nil
}
// Base64ToImage 将 Base64 字符串转换为 image.Image 格式
func Base64ToImage(base64Str string) (image.Image, error) {
// 使用正则表达式去除前缀
re := regexp.MustCompile(`^data:image/([a-zA-Z]*);base64,`)
// 去除前缀部分
base64Str = re.ReplaceAllString(base64Str, "")
// 解码 Base64 字符串
data, err := base64.StdEncoding.DecodeString(base64Str)
if err != nil {
return nil, fmt.Errorf("failed to decode base64 string: %v", err)
}
// 使用 image.Decode 解码字节数据
img, _, err := image.Decode(bytes.NewReader(data))
if err != nil {
return nil, fmt.Errorf("failed to decode image: %v", err)
}
return img, nil
}