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()
相关推荐
fsnine2 小时前
YOLOv2原理介绍
人工智能·计算机视觉·目标跟踪
m0_650108244 小时前
【论文精读】FlowVid:驯服不完美的光流,实现一致的视频到视频合成
人工智能·计算机视觉·扩散模型·视频编辑·视频生成·论文精读·不完美光流
程序猿小D5 小时前
【完整源码+数据集+部署教程】【零售和消费品&存货】价格标签检测系统源码&数据集全套:改进yolo11-RFAConv
前端·yolo·计算机视觉·目标跟踪·数据集·yolo11·价格标签检测系统源码
滑水滑成滑头5 小时前
**点云处理:发散创新,探索前沿技术**随着科技的飞速发展,点云处理技术在计算机视觉、自动驾驶、虚拟现实等领域的应用愈发广
java·python·科技·计算机视觉·自动驾驶
CoovallyAIHub6 小时前
超越“识别”:下一代机器视觉如何破解具身智能落地难题?
深度学习·算法·计算机视觉
CoovallyAIHub7 小时前
全球OCR新标杆!百度0.9B小模型斩获四项SOTA,读懂复杂文档像人一样自然
深度学习·算法·计算机视觉
Francek Chen7 小时前
【深度学习计算机视觉】14:实战Kaggle比赛:狗的品种识别(ImageNet Dogs)
人工智能·pytorch·深度学习·计算机视觉·kaggle·imagenet dogs
茜茜西西CeCe8 小时前
数字图像处理-图像编码与压缩
人工智能·计算机视觉·matlab·数字图像处理·图像压缩·图像编码
WWZZ202518 小时前
快速上手大模型:机器学习2(一元线性回归、代价函数、梯度下降法)
人工智能·算法·机器学习·计算机视觉·机器人·大模型·slam
2401_8588698019 小时前
目标检测2
人工智能·目标检测·计算机视觉