This shows you the differences between two versions of the page.
| Next revision | Previous revision | ||
| wiki:ai:azure_function_app_to_perform_image_blur [2025/07/03 17:11] – created ymurugesan | wiki:ai:azure_function_app_to_perform_image_blur [2025/07/03 18:20] (current) – ymurugesan | ||
|---|---|---|---|
| Line 5: | Line 5: | ||
| Install Azure CLI | Install Azure CLI | ||
| - | bash# Windows | + | # Windows |
| curl -sL https:// | curl -sL https:// | ||
| Line 14: | Line 14: | ||
| Install Azure Functions Core Tools | Install Azure Functions Core Tools | ||
| - | bash# Windows (via npm) | + | < |
| + | # Windows (via npm) | ||
| npm install -g azure-functions-core-tools@4 --unsafe-perm true | npm install -g azure-functions-core-tools@4 --unsafe-perm true | ||
| + | </ | ||
| + | < | ||
| # macOS | # macOS | ||
| brew tap azure/ | brew tap azure/ | ||
| brew install azure-functions-core-tools@4 | brew install azure-functions-core-tools@4 | ||
| + | </ | ||
| + | < | ||
| # Linux | # Linux | ||
| curl https:// | curl https:// | ||
| Line 28: | Line 32: | ||
| sudo apt-get install azure-functions-core-tools-4 | sudo apt-get install azure-functions-core-tools-4 | ||
| + | </ | ||
| + | |||
| + | Step 1: Login to Azure | ||
| + | < | ||
| + | az login | ||
| + | |||
| + | |||
| + | Step 2: Set Your Subscription (if you have multiple) | ||
| + | bash# List subscriptions | ||
| + | az account list --output table | ||
| + | |||
| + | # Set active subscription | ||
| + | |||
| + | az account set --subscription " | ||
| + | </ | ||
| + | |||
| + | Step 3: Create Resource Group (if needed) | ||
| + | bash | ||
| + | |||
| + | < | ||
| + | az group create --name myResourceGroup --location eastus | ||
| + | </ | ||
| + | |||
| + | Step 4: Create Storage Account (for Function App) | ||
| + | bash | ||
| + | < | ||
| + | az storage account create \ | ||
| + | --name myfunctionstorageacct \ | ||
| + | --location eastus \ | ||
| + | --resource-group myResourceGroup \ | ||
| + | --sku Standard_LRS | ||
| + | </ | ||
| + | Step 5: Create Function App | ||
| + | bash | ||
| + | |||
| + | < | ||
| + | az functionapp create \ | ||
| + | --resource-group myResourceGroup \ | ||
| + | --consumption-plan-location eastus \ | ||
| + | --runtime python \ | ||
| + | --runtime-version 3.9 \ | ||
| + | --functions-version 4 \ | ||
| + | --name myFaceBlurFunctionApp \ | ||
| + | --storage-account myfunctionstorageacct \ | ||
| + | --os-type linux | ||
| + | </ | ||
| + | |||
| + | Step 6: Prepare Your Project Structure | ||
| + | Create the following folder structure: | ||
| + | face-blur-function/ | ||
| + | ├── requirements.txt | ||
| + | ├── host.json | ||
| + | ├── local.settings.json | ||
| + | ├── FaceBlurTimer/ | ||
| + | │ | ||
| + | │ | ||
| + | └── shared/ | ||
| + | └── face_blur_processor.py | ||
| + | | ||
| + | | ||
| + | Step 7: Create Project Files | ||
| + | requirements.txt | ||
| + | < | ||
| + | txtazure-functions | ||
| + | azure-storage-blob | ||
| + | requests | ||
| + | python-dotenv | ||
| + | Pillow | ||
| + | schedule | ||
| + | </ | ||
| + | |||
| + | host.json | ||
| + | |||
| + | < | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | } | ||
| + | }, | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | }, | ||
| + | " | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | local.settings.json | ||
| + | < | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | } | ||
| + | < | ||
| + | |||
| + | FaceBlurTimer/ | ||
| + | < | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | ] | ||
| + | } | ||
| + | |||
| + | </ | ||
| + | Step 8: Set Application Settings | ||
| + | < | ||
| + | |||
| + | # Set your environment variables | ||
| + | az functionapp config appsettings set \ | ||
| + | --name myFaceBlurFunctionApp \ | ||
| + | --resource-group myResourceGroup \ | ||
| + | --settings \ | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | | ||
| + | </ | ||
| + | Step 9: Initialize Function Project Locally | ||
| + | |||
| + | < | ||
| + | # Create project directory | ||
| + | mkdir face-blur-function | ||
| + | cd face-blur-function | ||
| + | # Initialize function project | ||
| + | func init --python | ||
| + | </ | ||
| + | |||
| + | Step 10: Create Timer Function | ||
| + | |||
| + | < | ||
| + | |||
| + | func new --name FaceBlurTimer --template "Timer trigger" | ||
| + | |||
| + | < | ||
| + | |||
| + | Step 11: Deploy to Azure | ||
| + | |||
| + | < | ||
| + | # Deploy the function | ||
| + | func azure functionapp publish myFaceBlurFunctionApp | ||
| + | |||
| + | </ | ||
| + | |||
| + | Alternative: | ||
| + | If you prefer to deploy using zip: | ||
| + | < | ||
| + | Create deployment package | ||
| + | zip -r deployment.zip . | ||
| + | </ | ||
| + | |||
| + | # Deploy using az cli | ||
| + | |||
| + | < | ||
| + | az functionapp deployment source config-zip \ | ||
| + | --resource-group myResourceGroup \ | ||
| + | --name myFaceBlurFunctionApp \ | ||
| + | --src deployment.zip | ||
| + | |||
| + | </ | ||
| + | |||
| + | function_app.py | ||
| + | |||
| + | < | ||
| + | |||
| + | import azure.functions as func | ||
| + | import datetime | ||
| + | import json | ||
| + | import logging | ||
| + | |||
| + | app = func.FunctionApp() | ||
| + | </ | ||
| + | |||
| + | |||
| + | face_blur_processor.py | ||
| + | |||
| + | < | ||
| + | |||
| + | |||
| + | """ | ||
| + | Face Blur Processor Module for Azure Functions | ||
| + | """ | ||
| + | |||
| + | import os | ||
| + | import requests | ||
| + | import logging | ||
| + | import random | ||
| + | from datetime import datetime | ||
| + | from azure.storage.blob import BlobServiceClient | ||
| + | from azure.core.exceptions import AzureError | ||
| + | |||
| + | # Configure logging for Azure Functions | ||
| + | logger = logging.getLogger(__name__) | ||
| + | |||
| + | class FaceBlurProcessor: | ||
| + | def __init__(self): | ||
| + | # Azure Blob Storage configuration | ||
| + | self.connection_string = os.environ.get(' | ||
| + | self.container_name = os.environ.get(' | ||
| + | | ||
| + | # Web app configuration | ||
| + | webapp_url = os.environ.get(' | ||
| + | # Auto-fix URLs missing scheme | ||
| + | if not webapp_url.startswith((' | ||
| + | webapp_url = f' | ||
| + | self.webapp_url = webapp_url | ||
| + | self.webapp_timeout = int(os.environ.get(' | ||
| + | | ||
| + | # Initialize blob service client | ||
| + | if not self.connection_string: | ||
| + | raise ValueError(" | ||
| + | | ||
| + | self.blob_service_client = BlobServiceClient.from_connection_string(self.connection_string) | ||
| + | |||
| + | def ensure_container_exists(self): | ||
| + | """ | ||
| + | try: | ||
| + | container_client = self.blob_service_client.get_container_client(self.container_name) | ||
| + | if not container_client.exists(): | ||
| + | logger.info(f" | ||
| + | container_client.create_container() | ||
| + | logger.info(f" | ||
| + | return True | ||
| + | except AzureError as e: | ||
| + | logger.error(f" | ||
| + | return False | ||
| + | | ||
| + | def get_random_image(self): | ||
| + | """ | ||
| + | try: | ||
| + | # Ensure container exists first | ||
| + | if not self.ensure_container_exists(): | ||
| + | return None, None | ||
| + | | ||
| + | container_client = self.blob_service_client.get_container_client(self.container_name) | ||
| + | | ||
| + | # List all blobs | ||
| + | blobs = list(container_client.list_blobs()) | ||
| + | if not blobs: | ||
| + | logger.warning(f" | ||
| + | logger.info(" | ||
| + | return None, None | ||
| + | | ||
| + | # Select a random blob | ||
| + | random_blob = random.choice(blobs) | ||
| + | logger.info(f" | ||
| + | logger.info(f" | ||
| + | | ||
| + | return random_blob.name, | ||
| + | | ||
| + | except AzureError as e: | ||
| + | logger.error(f" | ||
| + | return None, None | ||
| + | | ||
| + | def get_latest_image(self): | ||
| + | """ | ||
| + | try: | ||
| + | # Ensure container exists first | ||
| + | if not self.ensure_container_exists(): | ||
| + | return None, None | ||
| + | | ||
| + | container_client = self.blob_service_client.get_container_client(self.container_name) | ||
| + | | ||
| + | # List all blobs and find the most recent one | ||
| + | blobs = list(container_client.list_blobs()) | ||
| + | if not blobs: | ||
| + | logger.warning(f" | ||
| + | logger.info(" | ||
| + | return None, None | ||
| + | | ||
| + | # Sort by last_modified to get the latest | ||
| + | latest_blob = max(blobs, key=lambda x: x.last_modified) | ||
| + | logger.info(f" | ||
| + | | ||
| + | return latest_blob.name, | ||
| + | | ||
| + | except AzureError as e: | ||
| + | logger.error(f" | ||
| + | return None, None | ||
| + | | ||
| + | def call_blur_webapp(self, | ||
| + | """ | ||
| + | try: | ||
| + | # Send the blob name as form data (matching your webapp' | ||
| + | data = {' | ||
| + | | ||
| + | logger.info(f" | ||
| + | response = requests.post( | ||
| + | self.webapp_url, | ||
| + | data=data, | ||
| + | timeout=self.webapp_timeout | ||
| + | ) | ||
| + | | ||
| + | logger.info(f" | ||
| + | logger.info(f" | ||
| + | | ||
| + | if response.status_code == 200: | ||
| + | logger.info(" | ||
| + | try: | ||
| + | response_data = response.json() | ||
| + | logger.info(f" | ||
| + | blob_url = response_data.get(' | ||
| + | if blob_url: | ||
| + | logger.info(f" | ||
| + | return blob_url | ||
| + | else: | ||
| + | logger.error(" | ||
| + | return None | ||
| + | except ValueError as e: | ||
| + | logger.error(f" | ||
| + | logger.error(f" | ||
| + | return None | ||
| + | else: | ||
| + | logger.error(f" | ||
| + | logger.error(f" | ||
| + | | ||
| + | # Parse error response | ||
| + | try: | ||
| + | error_data = response.json() | ||
| + | logger.error(f" | ||
| + | | ||
| + | # Check for specific Azure errors | ||
| + | error_msg = error_data.get(' | ||
| + | if ' | ||
| + | logger.error(" | ||
| + | elif ' | ||
| + | logger.error(" | ||
| + | elif ' | ||
| + | logger.error(" | ||
| + | elif ' | ||
| + | logger.info(" | ||
| + | return None # This might not be an error | ||
| + | | ||
| + | except ValueError: | ||
| + | logger.error(" | ||
| + | | ||
| + | return None | ||
| + | | ||
| + | except Exception as e: | ||
| + | logger.error(f" | ||
| + | return None | ||
| + | | ||
| + | def log_processed_image(self, | ||
| + | """ | ||
| + | try: | ||
| + | timestamp = datetime.now().strftime(" | ||
| + | | ||
| + | # Log the processing completion | ||
| + | logger.info(f" | ||
| + | logger.info(f" | ||
| + | logger.info(f" | ||
| + | | ||
| + | return blob_url | ||
| + | | ||
| + | except Exception as e: | ||
| + | logger.error(f" | ||
| + | return None | ||
| + | | ||
| + | def process_random_image(self): | ||
| + | """ | ||
| + | logger.info(" | ||
| + | | ||
| + | # Get random image | ||
| + | blob_name, blob_info = self.get_random_image() | ||
| + | if not blob_name: | ||
| + | return False | ||
| + | | ||
| + | try: | ||
| + | # Call web app to blur faces directly with blob name (no need to download) | ||
| + | blob_url = self.call_blur_webapp(blob_name) | ||
| + | if not blob_url: | ||
| + | return False | ||
| + | | ||
| + | # Log the processed image info (web app already uploaded it) | ||
| + | result = self.log_processed_image(blob_url, | ||
| + | if result: | ||
| + | logger.info(f" | ||
| + | return True | ||
| + | else: | ||
| + | return False | ||
| + | | ||
| + | except Exception as e: | ||
| + | logger.error(f" | ||
| + | return False | ||
| + | | ||
| + | def process_latest_image(self): | ||
| + | """ | ||
| + | logger.info(" | ||
| + | | ||
| + | # Get latest image | ||
| + | blob_name, blob_info = self.get_latest_image() | ||
| + | if not blob_name: | ||
| + | return False | ||
| + | | ||
| + | try: | ||
| + | # Call web app to blur faces directly with blob name (no need to download) | ||
| + | blob_url = self.call_blur_webapp(blob_name) | ||
| + | if not blob_url: | ||
| + | return False | ||
| + | | ||
| + | # Log the processed image info (web app already uploaded it) | ||
| + | result = self.log_processed_image(blob_url, | ||
| + | if result: | ||
| + | logger.info(f" | ||
| + | return True | ||
| + | else: | ||
| + | return False | ||
| + | | ||
| + | except Exception as e: | ||
| + | logger.error(f" | ||
| + | return False | ||
| + | |||
| + | </ | ||
| + | |||
| + | |||
| + | |||
| + | __init__.py | ||
| + | |||
| + | < | ||
| + | |||
| + | import datetime | ||
| + | import logging | ||
| + | import azure.functions as func | ||
| + | import sys | ||
| + | import os | ||
| + | |||
| + | # Add the parent directory to the path so we can import our modules | ||
| + | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||
| + | |||
| + | from shared.face_blur_processor import FaceBlurProcessor | ||
| + | |||
| + | |||
| + | def main(mytimer: | ||
| + | """ | ||
| + | Azure Function that runs on a timer to process random images for face blurring. | ||
| + | """ | ||
| + | utc_timestamp = datetime.datetime.utcnow().replace( | ||
| + | tzinfo=datetime.timezone.utc).isoformat() | ||
| + | if mytimer.past_due: | ||
| + | logging.info(' | ||
| + | logging.info(' | ||
| + | | ||
| + | try: | ||
| + | # Initialize the face blur processor | ||
| + | processor = FaceBlurProcessor() | ||
| + | | ||
| + | # Process a random image | ||
| + | success = processor.process_random_image() | ||
| + | | ||
| + | if success: | ||
| + | logging.info(" | ||
| + | else: | ||
| + | logging.error(" | ||
| + | | ||
| + | except Exception as e: | ||
| + | logging.error(f" | ||
| + | raise e | ||
| + | logging.info(' | ||
| + | | ||
| + | </ | ||