Amplitude Modulated (AM) Digital Halftoning

Amplitude Modulated (AM) Digital Halftoning

Key Steps

  1. Generate a threshold array

    • Typically small, such as 8×8 , 12×12 , or 16×16
    • Contains arranged threshold values used to modulate dot growth
  2. Tile the threshold array over the image

    • Repeat it across the entire image so each input pixel is matched to a threshold
    • The array acts like a stencil for comparing tones
  3. Compare each image pixel to the matching threshold value

    • For each pixel:
      Output={255if Intensity>Threshold0otherwise \text{Output} = \begin{cases} 255 & \text{if } \text{Intensity} > \text{Threshold} \\ 0 & \text{otherwise} \end{cases} Output={2550if Intensity>Thresholdotherwise
    • This produces a binary result per pixel: "on" (255) or "off" (0)
  4. Resulting pixels form clustered dots

    • Dot size grows with tone intensity
    • Dark tones → more "on" pixels in a tile; light tones → fewer

Note:

In AM halftoning , threshold values are usually arranged to produce round, square, or elliptical dot shapes and are reused across the image to maintain a regular grid structure.


detail

Steps to Build a Threshold Matrix

It's a small grid (like 8×8 or 16×16) containing values from 0 to 255 that define the dot shape and growth order. When tiled across an image, it tells each pixel when to turn black based on its tone.

🔹 1. Choose Cell Size & Screen Parameters

  • Pick cell size (e.g., 8×8 pixels)
  • Select screen frequency (dots/inch)
  • Choose a screen angle (dot orientation)

🔹 2. Assign Normalized Coordinates

Inside the cell:

  • Each pixel gets (x, y) ranging from −1 to +1

  • You rotate the coordinates to match the screen angle:

    math 复制代码
    x′ = x * cos(θ) − y * sin(θ)  
    y′ = x * sin(θ) + y * cos(θ)

🔹 3. Apply Spot Function

Choose a cost function to shape the dot---examples:

  1. Circle: x′2+y′2x'^2 + y'^2x′2+y′2
  2. Line: x′x'x′
  3. Square : max⁡(x′,y′)\max(x', y')max(x′,y′)
  4. Diamond: ∣x∣+∣y′∣|x|+ |y'|∣x∣+∣y′∣

This gives each pixel a cost, which defines when it should turn black as tone increases.

🔹 4. Histogram Equalization

  • Sort the cost values from low to high
  • Map them linearly to threshold values from 0--255
  • Ties (same cost) can be broken by angle to center or manually

🔹 5. Assemble the Matrix

You now have a full threshold matrix that:

  • Encodes dot shape
  • Responds smoothly to tone
  • Can be tiled across any grayscale image for halftoning

Would you like help building one programmatically or testing how different spot functions affect the pattern? I can simulate a couple matrix styles to show their real-world effect!

AM Halftoning Scenario Setup

Given a grayscale image: 300×300 pixels , and a printer with 600 DPI resolution, we want to convert the image into a halftone while:

  • Maintaining physical print size
  • Adapting to printer grid
  • Computing halftone cell size via screen frequency

Step 1: Image DPI and Physical Size

If the image is created at 300 DPI:

  • Physical size = \\frac{300 \\text{ pixels}}{300 \\text{ DPI}} = 1 \\text{ inch}

  • The printer uses 600 DPI , so to preserve the same printed size, we need to resample the image to:

    Resampled size=Original Pixel Width×Printer DPIImage DPI=300×600300=600 pixels \text{Resampled size} = \text{Original Pixel Width} × \frac{\text{Printer DPI}}{\text{Image DPI}} = 300 × \frac{600}{300} = 600 \text{ pixels} Resampled size=Original Pixel Width×Image DPIPrinter DPI=300×300600=600 pixels

Final resampled image: 600 × 600 pixels


Step 2: Screen Frequency and Cell Size

Screen frequency controls how many dot clusters (halftone cells) are printed per inch.

Let's say:

  • Screen frequency (LPI) = 60 lines/inch
  • Printer resolution (DPI) = 600 dots/inch

Then the cell size in pixels is:

Halftone Cell Size=Printer DPIScreen Frequency=60060=10 pixels \text{Halftone Cell Size} = \frac{\text{Printer DPI}}{\text{Screen Frequency}} = \frac{600}{60} = 10 \text{ pixels} Halftone Cell Size=Screen FrequencyPrinter DPI=60600=10 pixels

So each halftone cell covers:

  • 10 × 10 device pixels

The final image will have:

  • 60010=60 halftone cells per row or column \frac{600}{10} = 60 \text{ halftone cells per row or column} 10600=60 halftone cells per row or column
    Resulting in 60 × 60 dots forming the halftone pattern across the printed image

📌 Summary Table

Image DPI Printer DPI Screen Frequency (LPI) Resampled Size Cell Size (pixels) Dots Across Image
300 600 60 600 × 600 px 10 × 10 px 60 × 60

🛠️ Step-by-Step Guide (with Code)

⚙️ 1. Resample Image to Match DPI

Let's assume your screen frequency is 60 lines/inch → each halftone cell is:

python 复制代码
cell_size = int(600 / 60)  # = 10 pixels per cell

Now, convert your image to match printer resolution (600×600):

python 复制代码
import cv2

image = cv2.imread("input_image.jpg", cv2.IMREAD_GRAYSCALE)
image_resampled = cv2.resize(image, (600, 600), interpolation=cv2.INTER_CUBIC)

🧩 2. Define Spot Function (e.g., Circular)

python 复制代码
def spot_function(x, y):
    return x**2 + y**2

🔄 3. Tile Image & Apply Halftoning

python 复制代码
import numpy as np

def am_halftone(image, cell_size, angle_degrees):
    angle = np.deg2rad(angle_degrees)
    height, width = image.shape
    output = np.zeros_like(image)

    for i in range(0, height, cell_size):
        for j in range(0, width, cell_size):
            tile = image[i:i+cell_size, j:j+cell_size]
            cost_map = []
            for u in range(tile.shape[0]):
                for v in range(tile.shape[1]):
                    # Normalize coords
                    x = 2 * (u + 0.5)/cell_size - 1
                    y = 2 * (v + 0.5)/cell_size - 1
                    # Rotate
                    x_r = x * np.cos(angle) - y * np.sin(angle)
                    y_r = x * np.sin(angle) + y * np.cos(angle)
                    cost = spot_function(x_r, y_r)
                    cost_map.append((u, v, cost))
            # Histogram equalization
            cost_map.sort(key=lambda x: x[2])
            thresholds = np.linspace(0, 255, len(cost_map))
            for idx, (u, v, _) in enumerate(cost_map):
                thresh = thresholds[idx]
                output[i+u, j+v] = 0 if tile[u, v] > thresh else 255
    return output

📤 4. Run the Conversion

python 复制代码
halftone_image = am_halftone(image_resampled, cell_size=10, angle_degrees=45)
cv2.imwrite("halftone_output.png", halftone_image)

🔍 Key Insight

Resampling makes your image match the printer's pixel grid, so that each halftone cell aligns with real device pixels. That's critical---otherwise your dot shapes will be distorted or irregular.

Would you like to experiment with elliptical spot functions or visualize how dot shapes evolve when you change resolution or screen frequency? We can make a little halftoning playground together!

附录

符号说明

1. Physical size

  • Definition: The physical dimensions of an image when printed.

  • Units: Inches (in)

  • Formula:

    Physical Width (in)=Pixel WidthDPI,Physical Height (in)=Pixel HeightDPI \text{Physical Width (in)} = \frac{\text{Pixel Width}}{\text{DPI}}, \quad \text{Physical Height (in)} = \frac{\text{Pixel Height}}{\text{DPI}} Physical Width (in)=DPIPixel Width,Physical Height (in)=DPIPixel Height

  • Example: An image with 3000×2400 pixels at 300 DPI has a physical size of 10×8 inches.


2. DPI (Dots Per Inch)

  • Definition: How many dots a printer can place in one inch when printing.
  • Used for: Output resolution (printing).
  • Note: Often stored in image metadata (like TIFF, JPEG).
  • Common values: 72 (screen), 300 (high quality print), 600+ (laser printer).

3. PPI (Pixels Per Inch)

  • Definition: The pixel density of a digital image or screen---how many pixels are packed into one inch.

  • Used for: Screen/display resolution.

  • Clarification:

    • In images, PPI and DPI are often used interchangeably, but:

      • DPI is output-oriented (printing),
      • PPI is input/display-oriented (screens, raster images).

4. Resolution

  • Definition: General term referring to the level of detail in an image.

  • Can mean:

    • DPI (for physical output/print),
    • PPI (for screen/display),
    • Or pixel dimensions (width × height in pixels).

5. Pixel array size (pixel dimensions)

  • Definition: Number of pixels that make up the image.

  • Components:

    • Pixel Width: number of columns
    • Pixel Height: number of rows
  • Units: Pixels (px)

  • Example: 1920×1080 means 1920 columns and 1080 rows of pixels.


🔁 Relationships

If you know any two of:

  • pixel dimensions,
  • physical size,
  • resolution (DPI or PPI),

...you can compute the third.

Example:
  • Pixel size: 6000×4000
  • DPI: 300
    → Physical size = 20×13.33 inches

Summary Table

Term Definition Unit Applies To
Physical Size Size when printed Inches Print
DPI Dots per inch (printer dots) Dots/inch Print device
PPI Pixels per inch (image/screen density) Pixels/in Screen/Image
Resolution Level of detail DPI/PPI Varies
Pixel Size Number of pixels in width/height Pixels Image data

参考文献

https://engineering.purdue.edu/\~bouman/ece637/notes/

https://github.com/philgyford/python-halftone

介绍https://shankhya.github.io/musings/halftoning.pdf

https://stackoverflow.com/questions/10572274/how-to-create-cmyk-halftone-images-from-a-color-image/10575940#10575940

https://github.com/tfuxu/dither-go/tree/977f24c2d937eaff404f15270f29977c0719f5f8

研究人

https://ttwong12.github.io/

ReversibleHalftoning代码

https://github.com/MenghanXia/ReversibleHalftoning

艺术半调 https://github.com/setanarut/halftonism

源码

python 复制代码
import numpy as np
import cv2

def spot_function(x, y, mode='circle'):
    """
    Spot function for AM halftoning.
    mode: 'circle', 'line', 'square', 'diamond'
    """
    if mode == 'circle':
        return x**2 + y**2
    elif mode == 'line':
        return x
    elif mode == 'square':
        return max(abs(x), abs(y))
    elif mode == 'diamond':
        return abs(x) + abs(y)
    # 可扩展更多形状
    else:
        raise ValueError(f"Unknown spot function mode: {mode}")

def am_halftone(image, cell_size, angle_degrees, spot_mode='circle'):
    angle = np.deg2rad(angle_degrees)
    height, width = image.shape
    output = np.zeros_like(image)

    # 预先计算cost_map和thresholds
    cost_map = []
    for u in range(cell_size):
        for v in range(cell_size):
            x = 2 * (u + 0.5) / cell_size - 1
            y = 2 * (v + 0.5) / cell_size - 1
            x_r = x * np.cos(angle) - y * np.sin(angle)
            y_r = x * np.sin(angle) + y * np.cos(angle)
            cost = spot_function(x_r, y_r, mode=spot_mode)
            cost_map.append((u, v, cost))
    cost_map.sort(key=lambda x: x[2])
    thresholds = np.linspace(0, 255, len(cost_map))

    for i in range(0, height, cell_size):
        for j in range(0, width, cell_size):
            tile = image[i:i+cell_size, j:j+cell_size]
            for idx, (u, v, _) in enumerate(cost_map):
                if u < tile.shape[0] and v < tile.shape[1]:
                    thresh = thresholds[idx]
                    output[i+u, j+v] = 0 if tile[u, v] < thresh else 255
    return output

def main():
    import argparse
    parser = argparse.ArgumentParser(description="AM Halftoning")
    parser.add_argument("input_image", help="Path to input image (grayscale or RGB)")
    parser.add_argument("--cell_size", type=int, default=8, help="Cell size for halftoning")
    parser.add_argument("--angle", type=float, default=0, help="Screen angle for grayscale or Red channel")
    parser.add_argument("--angle_g", type=float, default=15, help="Screen angle for Green channel (RGB only)")
    parser.add_argument("--angle_b", type=float, default=30, help="Screen angle for Blue channel (RGB only)")
    parser.add_argument("--angle_a", type=float, default=45, help="Screen angle for Blue channel (RGB only)")
    parser.add_argument("--spot_mode", type=str, default="diamond", choices=["circle", "line", "square", "diamond"], help="Spot function mode")
    parser.add_argument("--output", type=str, default="E:\\code\\python\\Halftoning\\image\\halftone_diamond.bmp", help="Output image file name")
    args = parser.parse_args()

    image = cv2.imread(args.input_image, cv2.IMREAD_UNCHANGED)
    if image is None:
        print(f"Failed to load image: {args.input_image}")
        return
    print("image shape:", image.shape)
    if len(image.shape) == 2:  # Grayscale
        halftone_image = am_halftone(image, cell_size=args.cell_size, angle_degrees=args.angle, spot_mode=args.spot_mode)
    elif len(image.shape) == 3 and image.shape[2] == 3:  # RGB
        angles = [args.angle, args.angle_g, args.angle_b]
        channels = cv2.split(image)
        halftoned_channels = []
        for ch, ang in zip(channels, angles):
            halftoned = am_halftone(ch, cell_size=args.cell_size, angle_degrees=ang, spot_mode=args.spot_mode)
            halftoned_channels.append(halftoned)
        halftone_image = cv2.merge(halftoned_channels)
    elif len(image.shape) == 3 and image.shape[2] == 4:  # RGBA (actually BGRA in OpenCV)
        angles = [args.angle_b, args.angle_g, args.angle]  # B, G, R
        b, g, r, a = cv2.split(image)
        bgra_channels = [b, g, r]
        halftoned_bgr = []
        for ch, ang in zip(bgra_channels, angles):
            halftoned = am_halftone(ch, cell_size=args.cell_size, angle_degrees=ang, spot_mode=args.spot_mode)
            halftoned_bgr.append(halftoned)
        halftone_image = cv2.merge(halftoned_bgr + [a])
    else:
        print("Unsupported image format.")
        return

    cv2.imwrite(args.output, halftone_image)
    print(f"Halftone image saved to {args.output}")

if __name__ == "__main__":
    main()
相关推荐
这张生成的图像能检测吗1 小时前
(论文速读)EfficientTrain++: 高效视觉骨干训练的通用课程学习
人工智能·深度学习·计算机视觉·训练方法
翔云 OCR API7 小时前
人脸识别API开发者对接代码示例
开发语言·人工智能·python·计算机视觉·ocr
AndrewHZ8 小时前
【图像处理基石】如何在图像中提取出基本形状,比如圆形,椭圆,方形等等?
图像处理·python·算法·计算机视觉·cv·形状提取
音视频牛哥11 小时前
轻量级RTSP服务的工程化设计与应用:从移动端到边缘设备的实时媒体架构
人工智能·计算机视觉·音视频·音视频开发·rtsp播放器·安卓rtsp服务器·安卓实现ipc功能
audyxiao00116 小时前
期刊研究热点扫描|一文了解计算机视觉顶刊TIP的研究热点
人工智能·计算机视觉·transformer·图像分割·多模态
AI科技星17 小时前
为什么变化的电磁场才产生引力场?—— 统一场论揭示的时空动力学本质
数据结构·人工智能·经验分享·算法·计算机视觉
深蓝海拓17 小时前
opencv的模板匹配(Template Matching)学习笔记
人工智能·opencv·计算机视觉
Coding茶水间19 小时前
基于深度学习的路面坑洞检测系统演示与介绍(YOLOv12/v11/v8/v5模型+Pyqt5界面+训练代码+数据集)
图像处理·人工智能·深度学习·yolo·目标检测·计算机视觉
CoovallyAIHub20 小时前
如何在手机上轻松识别多种鸟类?我们发现了更简单的秘密……
深度学习·算法·计算机视觉
CoovallyAIHub21 小时前
抛弃LLM!MIT用纯视觉方法破解ARC难题,性能接近人类水平
深度学习·算法·计算机视觉