pulse-monitor/backups/backup_20251125/timestamp_ocr.go

110 lines
3.1 KiB
Go

package main
import (
"fmt"
"image"
"regexp"
"strconv"
"strings"
"time"
"github.com/otiai10/gosseract/v2"
"gocv.io/x/gocv"
)
// Global reusable OCR client
var timestampOCRClient *gosseract.Client
// InitTimestampOCR initializes the reusable OCR client
func InitTimestampOCR() {
timestampOCRClient = gosseract.NewClient()
timestampOCRClient.SetPageSegMode(gosseract.PSM_SINGLE_LINE)
timestampOCRClient.SetWhitelist("0123456789-: ")
}
// CloseTimestampOCR closes the OCR client
func CloseTimestampOCR() {
if timestampOCRClient != nil {
timestampOCRClient.Close()
}
}
// extractTimestamp extracts and OCRs the timestamp from the top of the frame
// Returns: (secondsDifference, ocrDurationMs, error)
func extractTimestamp(frame gocv.Mat) (int, int64, error) {
startTime := time.Now()
// Extract 1024x68 region
timestampRegion := frame.Region(image.Rect(0, 0, 1024, 68))
defer timestampRegion.Close()
// Downscale 50% (reduces OCR processing time)
downscaled := gocv.NewMat()
defer downscaled.Close()
gocv.Resize(timestampRegion, &downscaled, image.Point{512, 34}, 0, 0, gocv.InterpolationArea)
// Grayscale
gray := gocv.NewMat()
defer gray.Close()
gocv.CvtColor(downscaled, &gray, gocv.ColorBGRToGray)
// Binary threshold (Otsu automatically finds best threshold)
binary := gocv.NewMat()
defer binary.Close()
gocv.Threshold(gray, &binary, 0, 255, gocv.ThresholdBinary|gocv.ThresholdOtsu)
// Invert (white text on black → black text on white for better OCR)
inverted := gocv.NewMat()
defer inverted.Close()
gocv.BitwiseNot(binary, &inverted)
// Encode as PNG (lossless, better for text)
buf, err := gocv.IMEncode(".png", inverted)
if err != nil {
return 0, time.Since(startTime).Milliseconds(), fmt.Errorf("failed to encode: %w", err)
}
defer buf.Close()
// OCR with REUSED client (major speed boost)
err = timestampOCRClient.SetImageFromBytes(buf.GetBytes())
if err != nil {
return 0, time.Since(startTime).Milliseconds(), fmt.Errorf("failed to set image: %w", err)
}
text, err := timestampOCRClient.Text()
if err != nil {
return 0, time.Since(startTime).Milliseconds(), fmt.Errorf("OCR failed: %w", err)
}
// Parse timestamp
text = strings.TrimSpace(text)
text = strings.ReplaceAll(text, "\n", "")
// Pattern: YYYY-MM-DD HH:MM:SS
re := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})`)
matches := re.FindStringSubmatch(text)
if len(matches) != 7 {
return 0, time.Since(startTime).Milliseconds(), fmt.Errorf("could not parse timestamp from: '%s'", text)
}
// Parse components
year, _ := strconv.Atoi(matches[1])
month, _ := strconv.Atoi(matches[2])
day, _ := strconv.Atoi(matches[3])
hour, _ := strconv.Atoi(matches[4])
minute, _ := strconv.Atoi(matches[5])
second, _ := strconv.Atoi(matches[6])
// Create timestamp in local timezone
cameraTime := time.Date(year, time.Month(month), day, hour, minute, second, 0, time.Local)
serverTime := time.Now()
// Calculate difference
diff := int(serverTime.Sub(cameraTime).Seconds())
ocrDuration := time.Since(startTime).Milliseconds()
return diff, ocrDuration, nil
}