This commit is contained in:
sujit
2025-09-19 17:43:24 +05:45
parent 33857e32d1
commit 55db1dc4b5
3 changed files with 534 additions and 0 deletions

11
rename/go.mod Normal file
View File

@@ -0,0 +1,11 @@
module rename
go 1.24.2
require github.com/esimov/pigo v1.4.6
require (
github.com/corona10/goimagehash v1.1.0 // indirect
github.com/mattn/go-sqlite3 v1.14.32 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
)

17
rename/go.sum Normal file
View File

@@ -0,0 +1,17 @@
github.com/corona10/goimagehash v1.1.0 h1:teNMX/1e+Wn/AYSbLHX8mj+mF9r60R1kBeqE9MkoYwI=
github.com/corona10/goimagehash v1.1.0/go.mod h1:VkvE0mLn84L4aF8vCb6mafVajEb6QYMHl2ZJLn0mOGI=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/esimov/pigo v1.4.6 h1:wpB9FstbqeGP/CZP+nTR52tUJe7XErq8buG+k4xCXlw=
github.com/esimov/pigo v1.4.6/go.mod h1:uqj9Y3+3IRYhFK071rxz1QYq0ePhA6+R9jrUZavi46M=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

506
rename/main.go Normal file
View File

@@ -0,0 +1,506 @@
package main
/*
import (
"database/sql"
"fmt"
"image"
"image/color"
"image/draw"
"image/jpeg"
"image/png"
"io/ioutil"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/corona10/goimagehash"
pigo "github.com/esimov/pigo/core"
_ "github.com/mattn/go-sqlite3"
)
// FaceDetector wraps the Pigo classifier
type FaceDetector struct {
classifier *pigo.Pigo
}
// NewFaceDetector creates a new face detector instance
func NewFaceDetector(cascadeFile string) (*FaceDetector, error) {
cascadeData, err := ioutil.ReadFile(cascadeFile)
if err != nil {
return nil, fmt.Errorf("failed to read cascade file: %v", err)
}
p := pigo.NewPigo()
classifier, err := p.Unpack(cascadeData)
if err != nil {
return nil, fmt.Errorf("failed to unpack cascade: %v", err)
}
return &FaceDetector{
classifier: classifier,
}, nil
}
// DetectFaces detects faces in an image and returns detection results
func (fd *FaceDetector) DetectFaces(img image.Image, minSize, maxSize int, shiftFactor, scaleFactor float64, iouThreshold float64) []pigo.Detection {
// Convert image to grayscale
src := pigo.ImgToNRGBA(img)
pixels := pigo.RgbToGrayscale(src)
cols, rows := src.Bounds().Max.X, src.Bounds().Max.Y
// Canny edge detection parameters
cParams := pigo.CascadeParams{
MinSize: minSize,
MaxSize: maxSize,
ShiftFactor: shiftFactor,
ScaleFactor: scaleFactor,
ImageParams: pigo.ImageParams{
Pixels: pixels,
Rows: rows,
Cols: cols,
Dim: cols,
},
}
// Run the classifier over the obtained leaf nodes and return the detection results
dets := fd.classifier.RunCascade(cParams, 0.0)
// Calculate the intersection over union (IoU) of two clusters
dets = fd.classifier.ClusterDetections(dets, iouThreshold)
return dets
}
// DrawDetections draws bounding boxes around detected faces
func DrawDetections(img image.Image, detections []pigo.Detection) image.Image {
// Create a new RGBA image for drawing
bounds := img.Bounds()
dst := image.NewRGBA(bounds)
draw.Draw(dst, bounds, img, bounds.Min, draw.Src)
// Draw rectangles around detected faces
for _, det := range detections {
if det.Q > 150.0 { // Quality threshold (very high for best detection)
// Calculate rectangle coordinates
x1 := det.Col - det.Scale/2
y1 := det.Row - det.Scale/2
x2 := det.Col + det.Scale/2
y2 := det.Row + det.Scale/2
// Draw rectangle
drawRect(dst, x1, y1, x2, y2, color.RGBA{255, 0, 0, 255})
}
}
return dst
}
// drawRect draws a rectangle on the image
func drawRect(img *image.RGBA, x1, y1, x2, y2 int, col color.RGBA) {
// Draw horizontal lines
for x := x1; x <= x2; x++ {
if x >= 0 && x < img.Bounds().Max.X {
if y1 >= 0 && y1 < img.Bounds().Max.Y {
img.Set(x, y1, col)
}
if y2 >= 0 && y2 < img.Bounds().Max.Y {
img.Set(x, y2, col)
}
}
}
// Draw vertical lines
for y := y1; y <= y2; y++ {
if y >= 0 && y < img.Bounds().Max.Y {
if x1 >= 0 && x1 < img.Bounds().Max.X {
img.Set(x1, y, col)
}
if x2 >= 0 && x2 < img.Bounds().Max.X {
img.Set(x2, y, col)
}
}
}
}
// loadImage loads an image from file
func loadImage(filename string) (image.Image, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
ext := strings.ToLower(filepath.Ext(filename))
var img image.Image
switch ext {
case ".jpg", ".jpeg":
img, err = jpeg.Decode(file)
case ".png":
img, err = png.Decode(file)
default:
return nil, fmt.Errorf("unsupported image format: %s", ext)
}
return img, err
}
// saveImage saves an image to file
func saveImage(img image.Image, filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
ext := strings.ToLower(filepath.Ext(filename))
switch ext {
case ".jpg", ".jpeg":
return jpeg.Encode(file, img, &jpeg.Options{Quality: 95})
case ".png":
return png.Encode(file, img)
default:
return fmt.Errorf("unsupported output format: %s", ext)
}
}
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage:")
fmt.Println(" go run . <image_file> [output_file] (detect faces)")
fmt.Println(" go run . <image1> <image2> (compare faces)")
fmt.Println(" go run . add <name> <image_file> (add face to database)")
fmt.Println(" go run . recognize <image_file> (recognize face from database)")
os.Exit(1)
}
// Open database
db, err := sql.Open("sqlite3", "./faces.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Create table
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS faces (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
hash TEXT NOT NULL,
image_path TEXT
)`)
if err != nil {
log.Fatal(err)
}
// Create face detector
detector, err := NewFaceDetector("./cascade/facefinder")
if err != nil {
log.Fatalf("Failed to create face detector: %v", err)
}
if len(os.Args) > 2 && os.Args[1] == "add" {
if len(os.Args) < 4 {
fmt.Println("Usage: go run . add <name> <image_file>")
os.Exit(1)
}
name := os.Args[2]
imageFile := os.Args[3]
addFace(db, detector, name, imageFile)
} else if len(os.Args) > 2 && os.Args[1] == "recognize" {
if len(os.Args) < 3 {
fmt.Println("Usage: go run . recognize <image_file>")
os.Exit(1)
}
imageFile := os.Args[2]
recognizeFace(db, detector, imageFile)
} else {
// Original logic for detection/comparison
imageFile1 := os.Args[1]
var imageFile2 string
var outputFile string
if len(os.Args) > 2 {
// Check if second arg is an image file
if strings.HasSuffix(os.Args[2], ".jpg") || strings.HasSuffix(os.Args[2], ".jpeg") || strings.HasSuffix(os.Args[2], ".png") {
imageFile2 = os.Args[2]
if len(os.Args) > 3 {
outputFile = os.Args[3]
}
} else {
outputFile = os.Args[2]
}
}
if outputFile == "" {
ext := filepath.Ext(imageFile1)
outputFile = strings.TrimSuffix(imageFile1, ext) + "_detected" + ext
}
// Process first image
hashes1 := processImage(detector, imageFile1, outputFile)
if imageFile2 != "" {
// Process second image for comparison
outputFile2 := strings.TrimSuffix(imageFile2, filepath.Ext(imageFile2)) + "_detected" + filepath.Ext(imageFile2)
hashes2 := processImage(detector, imageFile2, outputFile2)
// Compare faces
compareFaces(hashes1, hashes2)
}
}
}
func processImage(detector *FaceDetector, imageFile, outputFile string) []*goimagehash.ImageHash {
// Load image
img, err := loadImage(imageFile)
if err != nil {
log.Fatalf("Failed to load image %s: %v", imageFile, err)
}
fmt.Printf("Image %s loaded: %dx%d\n", imageFile, img.Bounds().Dx(), img.Bounds().Dy())
// Detection parameters
minSize := 10 // Minimum face size
maxSize := 2000 // Maximum face size
shiftFactor := 0.05 // How much to shift the detection window (0.05 = 5%)
scaleFactor := 1.05 // How much to scale between detection sizes
iouThreshold := 0.4 // Intersection over Union threshold for clustering
// Detect faces
fmt.Printf("Detecting faces in %s...\n", imageFile)
detections := detector.DetectFaces(img, minSize, maxSize, shiftFactor, scaleFactor, iouThreshold)
// Filter detections by quality - very restrictive threshold
var validDetections []pigo.Detection
for _, det := range detections {
if det.Q > 150.0 { // Very high quality threshold to get only the best detection
validDetections = append(validDetections, det)
}
}
fmt.Printf("Found %d face(s) in %s\n", len(validDetections), imageFile)
// Print detection details
for i, det := range validDetections {
fmt.Printf("Face %d: Position(x=%d, y=%d), Size=%d, Quality=%.2f\n",
i+1, det.Col, det.Row, det.Scale, det.Q)
}
var faceHashes []*goimagehash.ImageHash
// Crop and save individual faces
for i, det := range validDetections {
// Calculate crop coordinates
x1 := det.Col - det.Scale/2
y1 := det.Row - det.Scale/2
x2 := det.Col + det.Scale/2
y2 := det.Row + det.Scale/2
// Ensure coordinates are within bounds
if x1 < 0 {
x1 = 0
}
if y1 < 0 {
y1 = 0
}
if x2 > img.Bounds().Max.X {
x2 = img.Bounds().Max.X
}
if y2 > img.Bounds().Max.Y {
y2 = img.Bounds().Max.Y
}
// Crop the face
faceRect := image.Rect(x1, y1, x2, y2)
faceImg := img.(interface {
SubImage(image.Rectangle) image.Image
}).SubImage(faceRect)
// Save the face
faceFilename := fmt.Sprintf("face_%s_%d.jpg", strings.TrimSuffix(filepath.Base(imageFile), filepath.Ext(imageFile)), i+1)
if err := saveImage(faceImg, faceFilename); err != nil {
log.Printf("Failed to save face %d: %v", i+1, err)
} else {
fmt.Printf("Saved face %d to: %s\n", i+1, faceFilename)
// Compute perceptual hash for face recognition
hash, err := goimagehash.PerceptionHash(faceImg)
if err != nil {
log.Printf("Failed to compute hash for face %d: %v", i+1, err)
} else {
fmt.Printf("Face %d hash: %s\n", i+1, hash.ToString())
faceHashes = append(faceHashes, hash)
}
}
}
// Draw detections on image
resultImg := DrawDetections(img, validDetections)
// Save result
if err := saveImage(resultImg, outputFile); err != nil {
log.Fatalf("Failed to save image: %v", err)
}
fmt.Printf("Result saved to: %s\n", outputFile)
return faceHashes
}
func compareFaces(hashes1, hashes2 []*goimagehash.ImageHash) {
if len(hashes1) == 0 || len(hashes2) == 0 {
fmt.Println("Cannot compare: one or both images have no faces")
return
}
// Compare first faces
hash1 := hashes1[0]
hash2 := hashes2[0]
distance, err := hash1.Distance(hash2)
if err != nil {
log.Printf("Failed to compute distance: %v", err)
return
}
fmt.Printf("Hash distance between faces: %d\n", distance)
// Threshold for similarity (lower distance means more similar)
threshold := 10
if distance <= threshold {
fmt.Println("Faces are likely the same person")
} else {
fmt.Println("Faces are likely different people")
}
}
func addFace(db *sql.DB, detector *FaceDetector, name, imageFile string) {
fmt.Printf("Adding face for %s from %s\n", name, imageFile)
// Process image to get hashes
outputFile := strings.TrimSuffix(imageFile, filepath.Ext(imageFile)) + "_detected" + filepath.Ext(imageFile)
hashes := processImage(detector, imageFile, outputFile)
if len(hashes) == 0 {
fmt.Println("No face found in image")
return
}
hash := hashes[0] // Use the first face
// Check if hash already exists
var existingName string
err := db.QueryRow("SELECT name FROM faces WHERE hash = ?", hash.ToString()).Scan(&existingName)
if err == nil {
fmt.Printf("Face already exists in database as: %s\n", existingName)
return
} else if err != sql.ErrNoRows {
log.Printf("Failed to check existing face: %v", err)
return
}
// Insert into database
_, err = db.Exec("INSERT INTO faces (name, hash, image_path) VALUES (?, ?, ?)", name, hash.ToString(), imageFile)
if err != nil {
log.Printf("Failed to insert face: %v", err)
return
}
fmt.Printf("Added face for %s to database\n", name)
}
func recognizeFace(db *sql.DB, detector *FaceDetector, imageFile string) {
fmt.Printf("Recognizing face in %s\n", imageFile)
// Process image to get hashes
outputFile := strings.TrimSuffix(imageFile, filepath.Ext(imageFile)) + "_detected" + filepath.Ext(imageFile)
hashes := processImage(detector, imageFile, outputFile)
if len(hashes) == 0 {
fmt.Println("No face found in image")
return
}
// Cluster hashes by similarity to avoid multiple detections of same person
var clusters [][]*goimagehash.ImageHash
for _, hash := range hashes {
found := false
for i, cluster := range clusters {
dist, _ := hash.Distance(cluster[0])
if dist <= 5 { // Same person threshold
clusters[i] = append(clusters[i], hash)
found = true
break
}
}
if !found {
clusters = append(clusters, []*goimagehash.ImageHash{hash})
}
}
fmt.Printf("Clustered into %d person(s)\n", len(clusters))
// Query all faces from database
rows, err := db.Query("SELECT name, hash FROM faces")
if err != nil {
log.Printf("Failed to query faces: %v", err)
return
}
defer rows.Close()
var bestMatch string
minDistance := 9999
for _, cluster := range clusters {
repHash := cluster[0] // Use first hash as representative
for rows.Next() {
var dbName, dbHashStr string
err := rows.Scan(&dbName, &dbHashStr)
if err != nil {
log.Printf("Failed to scan row: %v", err)
continue
}
// Parse hash string "p:hex"
parts := strings.Split(dbHashStr, ":")
if len(parts) != 2 {
log.Printf("Invalid hash format: %s", dbHashStr)
continue
}
hashValue, err := strconv.ParseUint(parts[1], 16, 64)
if err != nil {
log.Printf("Failed to parse hash value: %v", err)
continue
}
dbHash := goimagehash.NewImageHash(hashValue, goimagehash.PHash)
distance, err := repHash.Distance(dbHash)
if err != nil {
log.Printf("Failed to compute distance: %v", err)
continue
}
if distance < minDistance {
minDistance = distance
bestMatch = dbName
}
}
}
if bestMatch != "" && minDistance <= 10 {
fmt.Printf("Recognized as: %s (distance: %d)\n", bestMatch, minDistance)
if minDistance <= 5 {
fmt.Println("High confidence match")
} else {
fmt.Println("Low confidence match")
}
} else {
fmt.Println("No match found in database")
}
}
*/