From 55db1dc4b5e59a61adc18a54decc482a1553607c Mon Sep 17 00:00:00 2001 From: sujit Date: Fri, 19 Sep 2025 17:43:24 +0545 Subject: [PATCH] update --- rename/go.mod | 11 ++ rename/go.sum | 17 ++ rename/main.go | 506 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 534 insertions(+) create mode 100644 rename/go.mod create mode 100644 rename/go.sum create mode 100644 rename/main.go diff --git a/rename/go.mod b/rename/go.mod new file mode 100644 index 0000000..07781b0 --- /dev/null +++ b/rename/go.mod @@ -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 +) diff --git a/rename/go.sum b/rename/go.sum new file mode 100644 index 0000000..9aca45b --- /dev/null +++ b/rename/go.sum @@ -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= diff --git a/rename/main.go b/rename/main.go new file mode 100644 index 0000000..2cefc7a --- /dev/null +++ b/rename/main.go @@ -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 . [output_file] (detect faces)") + fmt.Println(" go run . (compare faces)") + fmt.Println(" go run . add (add face to database)") + fmt.Println(" go run . recognize (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 ") + 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 ") + 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") + } +} + +*/